diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:19:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:19:41 +0000 |
commit | 4f9fe856a25ab29345b90e7725509e9ee38a37be (patch) | |
tree | e4ffd8a9374cae7b21f7cbfb352927e0e074aff6 /src/tools | |
parent | Adding upstream version 1.68.2+dfsg1. (diff) | |
download | rustc-upstream/1.69.0+dfsg1.tar.xz rustc-upstream/1.69.0+dfsg1.zip |
Adding upstream version 1.69.0+dfsg1.upstream/1.69.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
792 files changed, 22496 insertions, 11880 deletions
diff --git a/src/tools/build_helper/src/git.rs b/src/tools/build_helper/src/git.rs index dc62051cb..168633c8f 100644 --- a/src/tools/build_helper/src/git.rs +++ b/src/tools/build_helper/src/git.rs @@ -1,5 +1,24 @@ +use std::process::Stdio; use std::{path::Path, process::Command}; +/// Runs a command and returns the output +fn output_result(cmd: &mut Command) -> Result<String, String> { + let output = match cmd.stderr(Stdio::inherit()).output() { + Ok(status) => status, + Err(e) => return Err(format!("failed to run command: {:?}: {}", cmd, e)), + }; + if !output.status.success() { + return Err(format!( + "command did not execute successfully: {:?}\n\ + expected success, got: {}\n{}", + cmd, + output.status, + String::from_utf8(output.stderr).map_err(|err| format!("{err:?}"))? + )); + } + Ok(String::from_utf8(output.stdout).map_err(|err| format!("{err:?}"))?) +} + /// Finds the remote for rust-lang/rust. /// For example for these remotes it will return `upstream`. /// ```text @@ -14,13 +33,7 @@ pub fn get_rust_lang_rust_remote(git_dir: Option<&Path>) -> Result<String, Strin git.current_dir(git_dir); } git.args(["config", "--local", "--get-regex", "remote\\..*\\.url"]); - - let output = git.output().map_err(|err| format!("{err:?}"))?; - if !output.status.success() { - return Err("failed to execute git config command".to_owned()); - } - - let stdout = String::from_utf8(output.stdout).map_err(|err| format!("{err:?}"))?; + let stdout = output_result(&mut git)?; let rust_lang_remote = stdout .lines() @@ -73,3 +86,48 @@ pub fn updated_master_branch(git_dir: Option<&Path>) -> Result<String, String> { // We could implement smarter logic here in the future. Ok("origin/master".into()) } + +/// Returns the files that have been modified in the current branch compared to the master branch. +/// The `extensions` parameter can be used to filter the files by their extension. +/// If `extensions` is empty, all files will be returned. +pub fn get_git_modified_files( + git_dir: Option<&Path>, + extensions: &Vec<&str>, +) -> Result<Option<Vec<String>>, String> { + let Ok(updated_master) = updated_master_branch(git_dir) else { return Ok(None); }; + + let git = || { + let mut git = Command::new("git"); + if let Some(git_dir) = git_dir { + git.current_dir(git_dir); + } + git + }; + + let merge_base = output_result(git().arg("merge-base").arg(&updated_master).arg("HEAD"))?; + let files = output_result(git().arg("diff-index").arg("--name-only").arg(merge_base.trim()))? + .lines() + .map(|s| s.trim().to_owned()) + .filter(|f| { + Path::new(f).extension().map_or(false, |ext| { + extensions.is_empty() || extensions.contains(&ext.to_str().unwrap()) + }) + }) + .collect(); + Ok(Some(files)) +} + +/// Returns the files that haven't been added to git yet. +pub fn get_git_untracked_files(git_dir: Option<&Path>) -> Result<Option<Vec<String>>, String> { + let Ok(_updated_master) = updated_master_branch(git_dir) else { return Ok(None); }; + let mut git = Command::new("git"); + if let Some(git_dir) = git_dir { + git.current_dir(git_dir); + } + + let files = output_result(git.arg("ls-files").arg("--others").arg("--exclude-standard"))? + .lines() + .map(|s| s.trim().to_owned()) + .collect(); + Ok(Some(files)) +} diff --git a/src/tools/bump-stage0/src/main.rs b/src/tools/bump-stage0/src/main.rs index 530a80b1e..f530a4d73 100644 --- a/src/tools/bump-stage0/src/main.rs +++ b/src/tools/bump-stage0/src/main.rs @@ -6,7 +6,7 @@ use std::convert::TryInto; const PATH: &str = "src/stage0.json"; const COMPILER_COMPONENTS: &[&str] = &["rustc", "rust-std", "cargo"]; -const RUSTFMT_COMPONENTS: &[&str] = &["rustfmt-preview"]; +const RUSTFMT_COMPONENTS: &[&str] = &["rustfmt-preview", "rustc"]; struct Tool { config: Config, diff --git a/src/tools/clippy/.github/workflows/clippy_bors.yml b/src/tools/clippy/.github/workflows/clippy_bors.yml index 1bc457a94..24e677ce8 100644 --- a/src/tools/clippy/.github/workflows/clippy_bors.yml +++ b/src/tools/clippy/.github/workflows/clippy_bors.yml @@ -157,6 +157,11 @@ jobs: - name: Test metadata collection run: cargo collect-metadata + - name: Test lint_configuration.md is up-to-date + run: | + echo "run \`cargo collect-metadata\` if this fails" + git update-index --refresh + integration_build: needs: changelog runs-on: ubuntu-latest diff --git a/src/tools/clippy/CHANGELOG.md b/src/tools/clippy/CHANGELOG.md index 8e31e8f0d..765826ed8 100644 --- a/src/tools/clippy/CHANGELOG.md +++ b/src/tools/clippy/CHANGELOG.md @@ -6,11 +6,204 @@ document. ## Unreleased / Beta / In Rust Nightly -[4f142aa1...master](https://github.com/rust-lang/rust-clippy/compare/4f142aa1...master) +[d822110d...master](https://github.com/rust-lang/rust-clippy/compare/d822110d...master) + +## Rust 1.67 + +Current stable, released 2023-01-26 + +[4f142aa1...d822110d](https://github.com/rust-lang/rust-clippy/compare/4f142aa1...d822110d) + +### New Lints + +* [`seek_from_current`] + [#9681](https://github.com/rust-lang/rust-clippy/pull/9681) +* [`from_raw_with_void_ptr`] + [#9690](https://github.com/rust-lang/rust-clippy/pull/9690) +* [`misnamed_getters`] + [#9770](https://github.com/rust-lang/rust-clippy/pull/9770) +* [`seek_to_start_instead_of_rewind`] + [#9667](https://github.com/rust-lang/rust-clippy/pull/9667) +* [`suspicious_xor_used_as_pow`] + [#9506](https://github.com/rust-lang/rust-clippy/pull/9506) +* [`unnecessary_safety_doc`] + [#9822](https://github.com/rust-lang/rust-clippy/pull/9822) +* [`unchecked_duration_subtraction`] + [#9570](https://github.com/rust-lang/rust-clippy/pull/9570) +* [`manual_is_ascii_check`] + [#9765](https://github.com/rust-lang/rust-clippy/pull/9765) +* [`unnecessary_safety_comment`] + [#9851](https://github.com/rust-lang/rust-clippy/pull/9851) +* [`let_underscore_future`] + [#9760](https://github.com/rust-lang/rust-clippy/pull/9760) +* [`manual_let_else`] + [#8437](https://github.com/rust-lang/rust-clippy/pull/8437) + +### Moves and Deprecations + +* Moved [`uninlined_format_args`] to `style` (Now warn-by-default) + [#9865](https://github.com/rust-lang/rust-clippy/pull/9865) +* Moved [`needless_collect`] to `nursery` (Now allow-by-default) + [#9705](https://github.com/rust-lang/rust-clippy/pull/9705) +* Moved [`or_fun_call`] to `nursery` (Now allow-by-default) + [#9829](https://github.com/rust-lang/rust-clippy/pull/9829) +* Uplifted [`let_underscore_lock`] into rustc + [#9697](https://github.com/rust-lang/rust-clippy/pull/9697) +* Uplifted [`let_underscore_drop`] into rustc + [#9697](https://github.com/rust-lang/rust-clippy/pull/9697) +* Moved [`bool_to_int_with_if`] to `pedantic` (Now allow-by-default) + [#9830](https://github.com/rust-lang/rust-clippy/pull/9830) +* Move `index_refutable_slice` to `pedantic` (Now warn-by-default) + [#9975](https://github.com/rust-lang/rust-clippy/pull/9975) +* Moved [`manual_clamp`] to `nursery` (Now allow-by-default) + [#10101](https://github.com/rust-lang/rust-clippy/pull/10101) + +### Enhancements + +* The scope of `#![clippy::msrv]` is now tracked correctly + [#9924](https://github.com/rust-lang/rust-clippy/pull/9924) +* `#[clippy::msrv]` can now be used as an outer attribute + [#9860](https://github.com/rust-lang/rust-clippy/pull/9860) +* Clippy will now avoid Cargo's cache, if `Cargo.toml` or `clippy.toml` have changed + [#9707](https://github.com/rust-lang/rust-clippy/pull/9707) +* [`uninlined_format_args`]: Added a new config `allow-mixed-uninlined-format-args` to allow the + lint, if only some arguments can be inlined + [#9865](https://github.com/rust-lang/rust-clippy/pull/9865) +* [`needless_lifetimes`]: Now provides suggests for individual lifetimes + [#9743](https://github.com/rust-lang/rust-clippy/pull/9743) +* [`needless_collect`]: Now detects needless `is_empty` and `contains` calls + [#8744](https://github.com/rust-lang/rust-clippy/pull/8744) +* [`blanket_clippy_restriction_lints`]: Now lints, if `clippy::restriction` is enabled via the + command line arguments + [#9755](https://github.com/rust-lang/rust-clippy/pull/9755) +* [`mutable_key_type`]: Now has the `ignore-interior-mutability` configuration, to add types which + should be ignored by the lint + [#9692](https://github.com/rust-lang/rust-clippy/pull/9692) +* [`uninlined_format_args`]: Now works for multiline `format!` expressions + [#9945](https://github.com/rust-lang/rust-clippy/pull/9945) +* [`cognitive_complexity`]: Now works for async functions + [#9828](https://github.com/rust-lang/rust-clippy/pull/9828) + [#9836](https://github.com/rust-lang/rust-clippy/pull/9836) +* [`vec_box`]: Now avoids an off-by-one error when using the `vec-box-size-threshold` configuration + [#9848](https://github.com/rust-lang/rust-clippy/pull/9848) +* [`never_loop`]: Now correctly handles breaks in nested labeled blocks + [#9858](https://github.com/rust-lang/rust-clippy/pull/9858) + [#9837](https://github.com/rust-lang/rust-clippy/pull/9837) +* [`disallowed_methods`], [`disallowed_types`], [`disallowed_macros`]: Now correctly resolve + paths, if a crate is used multiple times with different versions + [#9800](https://github.com/rust-lang/rust-clippy/pull/9800) +* [`disallowed_methods`]: Can now be used for local methods + [#9800](https://github.com/rust-lang/rust-clippy/pull/9800) +* [`print_stdout`], [`print_stderr`]: Can now be enabled in test with the `allow-print-in-tests` + config value + [#9797](https://github.com/rust-lang/rust-clippy/pull/9797) +* [`from_raw_with_void_ptr`]: Now works for `Rc`, `Arc`, `alloc::rc::Weak` and + `alloc::sync::Weak` types. + [#9700](https://github.com/rust-lang/rust-clippy/pull/9700) +* [`needless_borrowed_reference`]: Now works for struct and tuple patterns with wildcards + [#9855](https://github.com/rust-lang/rust-clippy/pull/9855) +* [`or_fun_call`]: Now supports `map_or` methods + [#9689](https://github.com/rust-lang/rust-clippy/pull/9689) +* [`unwrap_used`], [`expect_used`]: No longer lints in test code + [#9686](https://github.com/rust-lang/rust-clippy/pull/9686) +* [`fn_params_excessive_bools`]: Is now emitted with the lint level at the linted function + [#9698](https://github.com/rust-lang/rust-clippy/pull/9698) + +### False Positive Fixes + +* [`new_ret_no_self`]: No longer lints when `impl Trait<Self>` is returned + [#9733](https://github.com/rust-lang/rust-clippy/pull/9733) +* [`unnecessary_lazy_evaluations`]: No longer lints, if the type has a significant drop + [#9750](https://github.com/rust-lang/rust-clippy/pull/9750) +* [`option_if_let_else`]: No longer lints, if any arm has guard + [#9747](https://github.com/rust-lang/rust-clippy/pull/9747) +* [`explicit_auto_deref`]: No longer lints, if the target type is a projection with generic + arguments + [#9813](https://github.com/rust-lang/rust-clippy/pull/9813) +* [`unnecessary_to_owned`]: No longer lints, if the suggestion effects types + [#9796](https://github.com/rust-lang/rust-clippy/pull/9796) +* [`needless_borrow`]: No longer lints, if the suggestion is affected by `Deref` + [#9674](https://github.com/rust-lang/rust-clippy/pull/9674) +* [`unused_unit`]: No longer lints, if lifetimes are bound to the return type + [#9849](https://github.com/rust-lang/rust-clippy/pull/9849) +* [`mut_mut`]: No longer lints cases with unsized mutable references + [#9835](https://github.com/rust-lang/rust-clippy/pull/9835) +* [`bool_to_int_with_if`]: No longer lints in const context + [#9738](https://github.com/rust-lang/rust-clippy/pull/9738) +* [`use_self`]: No longer lints in macros + [#9704](https://github.com/rust-lang/rust-clippy/pull/9704) +* [`unnecessary_operation`]: No longer lints, if multiple macros are involved + [#9981](https://github.com/rust-lang/rust-clippy/pull/9981) +* [`allow_attributes_without_reason`]: No longer lints inside external macros + [#9630](https://github.com/rust-lang/rust-clippy/pull/9630) +* [`question_mark`]: No longer lints for `if let Err()` with an `else` branch + [#9722](https://github.com/rust-lang/rust-clippy/pull/9722) +* [`unnecessary_cast`]: No longer lints if the identifier and cast originate from different macros + [#9980](https://github.com/rust-lang/rust-clippy/pull/9980) +* [`arithmetic_side_effects`]: Now detects operations with associated constants + [#9592](https://github.com/rust-lang/rust-clippy/pull/9592) +* [`explicit_auto_deref`]: No longer lints, if the initial value is not a reference or reference + receiver + [#9997](https://github.com/rust-lang/rust-clippy/pull/9997) +* [`module_name_repetitions`], [`single_component_path_imports`]: Now handle `#[allow]` + attributes correctly + [#9879](https://github.com/rust-lang/rust-clippy/pull/9879) +* [`bool_to_int_with_if`]: No longer lints `if let` statements + [#9714](https://github.com/rust-lang/rust-clippy/pull/9714) +* [`needless_borrow`]: No longer lints, `if`-`else`-statements that require the borrow + [#9791](https://github.com/rust-lang/rust-clippy/pull/9791) +* [`needless_borrow`]: No longer lints borrows, if moves were illegal + [#9711](https://github.com/rust-lang/rust-clippy/pull/9711) +* [`manual_swap`]: No longer lints in const context + [#9871](https://github.com/rust-lang/rust-clippy/pull/9871) + +### Suggestion Fixes/Improvements + +* [`missing_safety_doc`], [`missing_errors_doc`], [`missing_panics_doc`]: No longer show the + entire item in the lint emission. + [#9772](https://github.com/rust-lang/rust-clippy/pull/9772) +* [`needless_lifetimes`]: Only suggests `'_` when it's applicable + [#9743](https://github.com/rust-lang/rust-clippy/pull/9743) +* [`use_self`]: Now suggests full paths correctly + [#9726](https://github.com/rust-lang/rust-clippy/pull/9726) +* [`redundant_closure_call`]: Now correctly deals with macros during suggestion creation + [#9987](https://github.com/rust-lang/rust-clippy/pull/9987) +* [`unnecessary_cast`]: Suggestions now correctly deal with references + [#9996](https://github.com/rust-lang/rust-clippy/pull/9996) +* [`unnecessary_join`]: Suggestions now correctly use [turbofish] operators + [#9779](https://github.com/rust-lang/rust-clippy/pull/9779) +* [`equatable_if_let`]: Can now suggest `matches!` replacements + [#9368](https://github.com/rust-lang/rust-clippy/pull/9368) +* [`string_extend_chars`]: Suggestions now correctly work for `str` slices + [#9741](https://github.com/rust-lang/rust-clippy/pull/9741) +* [`redundant_closure_for_method_calls`]: Suggestions now include angle brackets and generic + arguments if needed + [#9745](https://github.com/rust-lang/rust-clippy/pull/9745) +* [`manual_let_else`]: Suggestions no longer expand macro calls + [#9943](https://github.com/rust-lang/rust-clippy/pull/9943) +* [`infallible_destructuring_match`]: Suggestions now preserve references + [#9850](https://github.com/rust-lang/rust-clippy/pull/9850) +* [`result_large_err`]: The error now shows the largest enum variant + [#9662](https://github.com/rust-lang/rust-clippy/pull/9662) +* [`needless_return`]: Suggestions are now formatted better + [#9967](https://github.com/rust-lang/rust-clippy/pull/9967) +* [`unused_rounding`]: The suggestion now preserves the original float literal notation + [#9870](https://github.com/rust-lang/rust-clippy/pull/9870) + +[turbofish]: https://turbo.fish/::%3CClippy%3E + +### ICE Fixes + +* [`result_large_err`]: Fixed ICE for empty enums + [#10007](https://github.com/rust-lang/rust-clippy/pull/10007) +* [`redundant_allocation`]: Fixed ICE for types with bounded variables + [#9773](https://github.com/rust-lang/rust-clippy/pull/9773) +* [`unused_rounding`]: Fixed ICE, if `_` was used as a separator + [#10001](https://github.com/rust-lang/rust-clippy/pull/10001) ## Rust 1.66 -Current stable, released 2022-12-15 +Released 2022-12-15 [b52fb523...4f142aa1](https://github.com/rust-lang/rust-clippy/compare/b52fb523...4f142aa1) @@ -166,6 +359,7 @@ Current stable, released 2022-12-15 * [`unnecessary_to_owned`]: Avoid ICEs in favor of false negatives if information is missing [#9505](https://github.com/rust-lang/rust-clippy/pull/9505) + [#10027](https://github.com/rust-lang/rust-clippy/pull/10027) * [`manual_range_contains`]: No longer ICEs on values behind references [#9627](https://github.com/rust-lang/rust-clippy/pull/9627) * [`needless_pass_by_value`]: No longer ICEs on unsized `dyn Fn` arguments @@ -4189,6 +4383,7 @@ Released 2018-09-13 [`extend_from_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#extend_from_slice [`extend_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#extend_with_drain [`extra_unused_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#extra_unused_lifetimes +[`extra_unused_type_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#extra_unused_type_parameters [`fallible_impl_from`]: https://rust-lang.github.io/rust-clippy/master/index.html#fallible_impl_from [`field_reassign_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#field_reassign_with_default [`filetype_is_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#filetype_is_file @@ -4235,6 +4430,7 @@ Released 2018-09-13 [`if_same_then_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_same_then_else [`if_then_some_else_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_then_some_else_none [`ifs_same_cond`]: https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond +[`impl_trait_in_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#impl_trait_in_params [`implicit_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_clone [`implicit_hasher`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_hasher [`implicit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_return @@ -4299,6 +4495,7 @@ Released 2018-09-13 [`let_underscore_future`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_future [`let_underscore_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_lock [`let_underscore_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_must_use +[`let_underscore_untyped`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_untyped [`let_unit_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_unit_value [`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist [`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug @@ -4383,6 +4580,7 @@ Released 2018-09-13 [`multi_assignments`]: https://rust-lang.github.io/rust-clippy/master/index.html#multi_assignments [`multiple_crate_versions`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions [`multiple_inherent_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl +[`multiple_unsafe_ops_per_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_unsafe_ops_per_block [`must_use_candidate`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_candidate [`must_use_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_unit [`mut_from_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_from_ref @@ -4424,6 +4622,7 @@ Released 2018-09-13 [`no_effect`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect [`no_effect_replace`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_replace [`no_effect_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_underscore_binding +[`no_mangle_with_rust_abi`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_mangle_with_rust_abi [`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal [`non_octal_unix_permissions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_octal_unix_permissions [`non_send_fields_in_send_ty`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_send_fields_in_send_ty @@ -4479,6 +4678,7 @@ Released 2018-09-13 [`pub_enum_variant_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_enum_variant_names [`pub_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#pub_use [`question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#question_mark +[`question_mark_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#question_mark_used [`range_minus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_minus_one [`range_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_plus_one [`range_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_step_by_zero @@ -4538,6 +4738,7 @@ Released 2018-09-13 [`should_assert_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_assert_eq [`should_implement_trait`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait [`significant_drop_in_scrutinee`]: https://rust-lang.github.io/rust-clippy/master/index.html#significant_drop_in_scrutinee +[`significant_drop_tightening`]: https://rust-lang.github.io/rust-clippy/master/index.html#significant_drop_tightening [`similar_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#similar_names [`single_char_add_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_add_str [`single_char_lifetime_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_lifetime_names @@ -4568,6 +4769,7 @@ Released 2018-09-13 [`suboptimal_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#suboptimal_flops [`suspicious_arithmetic_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl [`suspicious_assignment_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_assignment_formatting +[`suspicious_command_arg_space`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_command_arg_space [`suspicious_else_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_else_formatting [`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map [`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl @@ -4594,6 +4796,7 @@ Released 2018-09-13 [`transmute_int_to_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_bool [`transmute_int_to_char`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_char [`transmute_int_to_float`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_float +[`transmute_int_to_non_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_non_zero [`transmute_null_to_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_null_to_fn [`transmute_num_to_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_num_to_bytes [`transmute_ptr_to_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ptr diff --git a/src/tools/clippy/Cargo.toml b/src/tools/clippy/Cargo.toml index f8cb4b721..70d126809 100644 --- a/src/tools/clippy/Cargo.toml +++ b/src/tools/clippy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy" -version = "0.1.68" +version = "0.1.69" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" @@ -42,6 +42,7 @@ filetime = "0.2" rustc-workspace-hack = "1.0" # UI test dependencies +clap = { version = "4.1.4", features = ["derive"] } clippy_utils = { path = "clippy_utils" } derive-new = "0.5" if_chain = "1.0" diff --git a/src/tools/clippy/README.md b/src/tools/clippy/README.md index 81254ba8b..3e7379ace 100644 --- a/src/tools/clippy/README.md +++ b/src/tools/clippy/README.md @@ -5,7 +5,7 @@ A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. -[There are over 550 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) +[There are over 600 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category. @@ -19,21 +19,35 @@ You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the | `clippy::complexity` | code that does something simple but in a complex way | **warn** | | `clippy::perf` | code that can be written to run faster | **warn** | | `clippy::pedantic` | lints which are rather strict or have occasional false positives | allow | +| `clippy::restriction` | lints which prevent the use of language and library features[^restrict] | allow | | `clippy::nursery` | new lints that are still under development | allow | | `clippy::cargo` | lints for the cargo manifest | allow | More to come, please [file an issue](https://github.com/rust-lang/rust-clippy/issues) if you have ideas! -The [lint list](https://rust-lang.github.io/rust-clippy/master/index.html) also contains "restriction lints", which are -for things which are usually not considered "bad", but may be useful to turn on in specific cases. These should be used -very selectively, if at all. +The `restriction` category should, *emphatically*, not be enabled as a whole. The contained +lints may lint against perfectly reasonable code, may not have an alternative suggestion, +and may contradict any other lints (including other categories). Lints should be considered +on a case-by-case basis before enabling. + +[^restrict]: Some use cases for `restriction` lints include: + - Strict coding styles (e.g. [`clippy::else_if_without_else`]). + - Additional restrictions on CI (e.g. [`clippy::todo`]). + - Preventing panicking in certain functions (e.g. [`clippy::unwrap_used`]). + - Running a lint only on a subset of code (e.g. `#[forbid(clippy::float_arithmetic)]` on a module). + +[`clippy::else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else +[`clippy::todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo +[`clippy::unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used + +--- Table of contents: -* [Usage instructions](#usage) -* [Configuration](#configuration) -* [Contributing](#contributing) -* [License](#license) +* [Usage instructions](#usage) +* [Configuration](#configuration) +* [Contributing](#contributing) +* [License](#license) ## Usage @@ -64,6 +78,7 @@ Once you have rustup and the latest stable release (at least Rust 1.29) installe ```terminal rustup component add clippy ``` + If it says that it can't find the `clippy` component, please run `rustup self update`. #### Step 3: Run Clippy @@ -143,16 +158,16 @@ line. (You can swap `clippy::all` with the specific lint category you are target You can add options to your code to `allow`/`warn`/`deny` Clippy lints: -* the whole set of `Warn` lints using the `clippy` lint group (`#![deny(clippy::all)]`). +* the whole set of `Warn` lints using the `clippy` lint group (`#![deny(clippy::all)]`). Note that `rustc` has additional [lint groups](https://doc.rust-lang.org/rustc/lints/groups.html). -* all lints using both the `clippy` and `clippy::pedantic` lint groups (`#![deny(clippy::all)]`, +* all lints using both the `clippy` and `clippy::pedantic` lint groups (`#![deny(clippy::all)]`, `#![deny(clippy::pedantic)]`). Note that `clippy::pedantic` contains some very aggressive lints prone to false positives. -* only some lints (`#![deny(clippy::single_match, clippy::box_vec)]`, etc.) +* only some lints (`#![deny(clippy::single_match, clippy::box_vec)]`, etc.) -* `allow`/`warn`/`deny` can be limited to a single function or module using `#[allow(...)]`, etc. +* `allow`/`warn`/`deny` can be limited to a single function or module using `#[allow(...)]`, etc. Note: `allow` means to suppress the lint for your code. With `warn` the lint will only emit a warning, while with `deny` the lint will emit an error, when @@ -176,12 +191,14 @@ cargo clippy -- -W clippy::lint_name This also works with lint groups. For example, you can run Clippy with warnings for all lints enabled: + ```terminal cargo clippy -- -W clippy::pedantic ``` If you care only about a single lint, you can allow all others and then explicitly warn on the lint(s) you are interested in: + ```terminal cargo clippy -- -A clippy::all -W clippy::useless_format -W clippy::... ``` @@ -194,11 +211,21 @@ value` mapping e.g. ```toml avoid-breaking-exported-api = false disallowed-names = ["toto", "tata", "titi"] -cognitive-complexity-threshold = 30 ``` -See the [list of configurable lints](https://rust-lang.github.io/rust-clippy/master/index.html#Configuration), -the lint descriptions contain the names and meanings of these configuration variables. +The [table of configurations](https://doc.rust-lang.org/nightly/clippy/lint_configuration.html) +contains all config values, their default, and a list of lints they affect. +Each [configurable lint](https://rust-lang.github.io/rust-clippy/master/index.html#Configuration) +, also contains information about these values. + +For configurations that are a list type with default values such as +[disallowed-names](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names), +you can use the unique value `".."` to extend the default values instead of replacing them. + +```toml +# default of disallowed-names is ["foo", "baz", "quux"] +disallowed-names = ["bar", ".."] # -> ["bar", "foo", "baz", "quux"] +``` > **Note** > diff --git a/src/tools/clippy/book/src/README.md b/src/tools/clippy/book/src/README.md index 23867df8e..df4a1f270 100644 --- a/src/tools/clippy/book/src/README.md +++ b/src/tools/clippy/book/src/README.md @@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. -[There are over 550 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) +[There are over 600 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how diff --git a/src/tools/clippy/book/src/SUMMARY.md b/src/tools/clippy/book/src/SUMMARY.md index 1f0b8db28..0649f7a63 100644 --- a/src/tools/clippy/book/src/SUMMARY.md +++ b/src/tools/clippy/book/src/SUMMARY.md @@ -5,6 +5,7 @@ - [Installation](installation.md) - [Usage](usage.md) - [Configuration](configuration.md) + - [Lint Configuration](lint_configuration.md) - [Clippy's Lints](lints.md) - [Continuous Integration](continuous_integration/README.md) - [GitHub Actions](continuous_integration/github_actions.md) diff --git a/src/tools/clippy/book/src/configuration.md b/src/tools/clippy/book/src/configuration.md index 430ff8b73..87f4a697a 100644 --- a/src/tools/clippy/book/src/configuration.md +++ b/src/tools/clippy/book/src/configuration.md @@ -8,11 +8,21 @@ basic `variable = value` mapping eg. ```toml avoid-breaking-exported-api = false disallowed-names = ["toto", "tata", "titi"] -cognitive-complexity-threshold = 30 ``` -See the [list of configurable lints](https://rust-lang.github.io/rust-clippy/master/index.html#Configuration), -the lint descriptions contain the names and meanings of these configuration variables. +The [table of configurations](./lint_configuration.md) +contains all config values, their default, and a list of lints they affect. +Each [configurable lint](https://rust-lang.github.io/rust-clippy/master/index.html#Configuration) +, also contains information about these values. + +For configurations that are a list type with default values such as +[disallowed-names](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names), +you can use the unique value `".."` to extend the default values instead of replacing them. + +```toml +# default of disallowed-names is ["foo", "baz", "quux"] +disallowed-names = ["bar", ".."] # -> ["bar", "foo", "baz", "quux"] +``` To deactivate the "for further information visit *lint-link*" message you can define the `CLIPPY_DISABLE_DOCS_LINKS` environment variable. diff --git a/src/tools/clippy/book/src/development/adding_lints.md b/src/tools/clippy/book/src/development/adding_lints.md index 8b4eee8c9..f57dc627d 100644 --- a/src/tools/clippy/book/src/development/adding_lints.md +++ b/src/tools/clippy/book/src/development/adding_lints.md @@ -146,7 +146,8 @@ For cargo lints, the process of testing differs in that we are interested in the manifest. If our new lint is named e.g. `foo_categories`, after running `cargo dev -new_lint` we will find by default two new crates, each with its manifest file: +new_lint --name=foo_categories --type=cargo --category=cargo` we will find by +default two new crates, each with its manifest file: * `tests/ui-cargo/foo_categories/fail/Cargo.toml`: this file should cause the new lint to raise an error. @@ -699,6 +700,10 @@ for some users. Adding a configuration is done in the following steps: `clippy.toml` file with the configuration value and a rust file that should be linted by Clippy. The test can otherwise be written as usual. +5. Update [Lint Configuration](../lint_configuration.md) + + Run `cargo collect-metadata` to generate documentation changes for the book. + [`clippy_lints::utils::conf`]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/utils/conf.rs [`clippy_lints` lib file]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/lib.rs [`tests/ui`]: https://github.com/rust-lang/rust-clippy/blob/master/tests/ui diff --git a/src/tools/clippy/book/src/development/infrastructure/backport.md b/src/tools/clippy/book/src/development/infrastructure/backport.md index 15f3d1f08..6920c4e46 100644 --- a/src/tools/clippy/book/src/development/infrastructure/backport.md +++ b/src/tools/clippy/book/src/development/infrastructure/backport.md @@ -28,6 +28,7 @@ repository. You can do this with: ```bash # Assuming the current directory corresponds to the Rust repository $ git checkout beta +# Make sure to change `your-github-name` to your github name in the following command $ git subtree pull -p src/tools/clippy https://github.com/<your-github-name>/rust-clippy backport $ ./x.py test src/tools/clippy ``` diff --git a/src/tools/clippy/book/src/development/infrastructure/book.md b/src/tools/clippy/book/src/development/infrastructure/book.md index a48742191..dbd624ecd 100644 --- a/src/tools/clippy/book/src/development/infrastructure/book.md +++ b/src/tools/clippy/book/src/development/infrastructure/book.md @@ -3,15 +3,15 @@ This document explains how to make additions and changes to the Clippy book, the guide to Clippy that you're reading right now. The Clippy book is formatted with [Markdown](https://www.markdownguide.org) and generated by -[mdbook](https://github.com/rust-lang/mdBook). +[mdBook](https://github.com/rust-lang/mdBook). -- [Get mdbook](#get-mdbook) +- [Get mdBook](#get-mdbook) - [Make changes](#make-changes) -## Get mdbook +## Get mdBook While not strictly necessary since the book source is simply Markdown text -files, having mdbook locally will allow you to build, test and serve the book +files, having mdBook locally will allow you to build, test and serve the book locally to view changes before you commit them to the repository. You likely already have `cargo` installed, so the easiest option is to simply: @@ -19,7 +19,7 @@ already have `cargo` installed, so the easiest option is to simply: cargo install mdbook ``` -See the mdbook [installation](https://github.com/rust-lang/mdBook#installation) +See the mdBook [installation](https://github.com/rust-lang/mdBook#installation) instructions for other options. ## Make changes @@ -27,7 +27,7 @@ instructions for other options. The book's [src](https://github.com/rust-lang/rust-clippy/tree/master/book/src) directory contains all of the markdown files used to generate the book. If you -want to see your changes in real time, you can use the mdbook `serve` command to +want to see your changes in real time, you can use the mdBook `serve` command to run a web server locally that will automatically update changes as they are made. From the top level of your `rust-clippy` directory: @@ -38,5 +38,5 @@ mdbook serve book --open Then navigate to `http://localhost:3000` to see the generated book. While the server is running, changes you make will automatically be updated. -For more information, see the mdbook +For more information, see the mdBook [guide](https://rust-lang.github.io/mdBook/). diff --git a/src/tools/clippy/book/src/development/infrastructure/changelog_update.md b/src/tools/clippy/book/src/development/infrastructure/changelog_update.md index 80a47affe..d1ac7237b 100644 --- a/src/tools/clippy/book/src/development/infrastructure/changelog_update.md +++ b/src/tools/clippy/book/src/development/infrastructure/changelog_update.md @@ -95,11 +95,23 @@ As section headers, we use: Please also be sure to update the Beta/Unreleased sections at the top with the relevant commit ranges. -If you have the time, it would be appreciated if you double-check, that the -`#[clippy::version]` attributes for the added lints contains the correct version. +#### 3.1 Include `beta-accepted` PRs + +Look for the [`beta-accepted`] label and make sure to also include the PRs with +that label in the changelog. If you can, remove the `beta-accepted` labels +**after** the changelog PR was merged. + +> _Note:_ Some of those PRs might even got backported to the previous `beta`. +> Those have to be included in the changelog of the _previous_ release. + +### 4. Update `clippy::version` attributes + +Next, make sure to check that the `#[clippy::version]` attributes for the added +lints contain the correct version. [changelog]: https://github.com/rust-lang/rust-clippy/blob/master/CHANGELOG.md [forge]: https://forge.rust-lang.org/ [rust_master_tools]: https://github.com/rust-lang/rust/tree/master/src/tools/clippy [rust_beta_tools]: https://github.com/rust-lang/rust/tree/beta/src/tools/clippy [rust_stable_tools]: https://github.com/rust-lang/rust/releases +[`beta-accepted`]: https://github.com/rust-lang/rust-clippy/issues?q=label%3Abeta-accepted+ diff --git a/src/tools/clippy/book/src/development/infrastructure/sync.md b/src/tools/clippy/book/src/development/infrastructure/sync.md index 5a0f7409a..02cfc11b5 100644 --- a/src/tools/clippy/book/src/development/infrastructure/sync.md +++ b/src/tools/clippy/book/src/development/infrastructure/sync.md @@ -79,8 +79,7 @@ to be run inside the `rust` directory): `rustup check`. 3. Sync the changes to the rust-copy of Clippy to your Clippy fork: ```bash - # Make sure to change `your-github-name` to your github name in the following command. Also be - # sure to either use a net-new branch, e.g. `sync-from-rust`, or delete the branch beforehand + # Be sure to either use a net-new branch, e.g. `sync-from-rust`, or delete the branch beforehand # because changes cannot be fast forwarded and you have to run this command again. git subtree push -P src/tools/clippy clippy-local sync-from-rust ``` diff --git a/src/tools/clippy/book/src/lint_configuration.md b/src/tools/clippy/book/src/lint_configuration.md new file mode 100644 index 000000000..995dd2f04 --- /dev/null +++ b/src/tools/clippy/book/src/lint_configuration.md @@ -0,0 +1,553 @@ +<!-- +This file is generated by `cargo collect-metadata`. +Please use that command to update the file and do not edit it by hand. +--> + +## Lint Configuration Options +| <div style="width:290px">Option</div> | Default Value | +|--|--| +| [arithmetic-side-effects-allowed](#arithmetic-side-effects-allowed) | `{}` | +| [arithmetic-side-effects-allowed-binary](#arithmetic-side-effects-allowed-binary) | `[]` | +| [arithmetic-side-effects-allowed-unary](#arithmetic-side-effects-allowed-unary) | `{}` | +| [avoid-breaking-exported-api](#avoid-breaking-exported-api) | `true` | +| [msrv](#msrv) | `None` | +| [cognitive-complexity-threshold](#cognitive-complexity-threshold) | `25` | +| [disallowed-names](#disallowed-names) | `["foo", "baz", "quux"]` | +| [doc-valid-idents](#doc-valid-idents) | `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", "WebGL", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` | +| [too-many-arguments-threshold](#too-many-arguments-threshold) | `7` | +| [type-complexity-threshold](#type-complexity-threshold) | `250` | +| [single-char-binding-names-threshold](#single-char-binding-names-threshold) | `4` | +| [too-large-for-stack](#too-large-for-stack) | `200` | +| [enum-variant-name-threshold](#enum-variant-name-threshold) | `3` | +| [enum-variant-size-threshold](#enum-variant-size-threshold) | `200` | +| [verbose-bit-mask-threshold](#verbose-bit-mask-threshold) | `1` | +| [literal-representation-threshold](#literal-representation-threshold) | `16384` | +| [trivial-copy-size-limit](#trivial-copy-size-limit) | `None` | +| [pass-by-value-size-limit](#pass-by-value-size-limit) | `256` | +| [too-many-lines-threshold](#too-many-lines-threshold) | `100` | +| [array-size-threshold](#array-size-threshold) | `512000` | +| [vec-box-size-threshold](#vec-box-size-threshold) | `4096` | +| [max-trait-bounds](#max-trait-bounds) | `3` | +| [max-struct-bools](#max-struct-bools) | `3` | +| [max-fn-params-bools](#max-fn-params-bools) | `3` | +| [warn-on-all-wildcard-imports](#warn-on-all-wildcard-imports) | `false` | +| [disallowed-macros](#disallowed-macros) | `[]` | +| [disallowed-methods](#disallowed-methods) | `[]` | +| [disallowed-types](#disallowed-types) | `[]` | +| [unreadable-literal-lint-fractions](#unreadable-literal-lint-fractions) | `true` | +| [upper-case-acronyms-aggressive](#upper-case-acronyms-aggressive) | `false` | +| [matches-for-let-else](#matches-for-let-else) | `WellKnownTypes` | +| [cargo-ignore-publish](#cargo-ignore-publish) | `false` | +| [standard-macro-braces](#standard-macro-braces) | `[]` | +| [enforced-import-renames](#enforced-import-renames) | `[]` | +| [allowed-scripts](#allowed-scripts) | `["Latin"]` | +| [enable-raw-pointer-heuristic-for-send](#enable-raw-pointer-heuristic-for-send) | `true` | +| [max-suggested-slice-pattern-length](#max-suggested-slice-pattern-length) | `3` | +| [await-holding-invalid-types](#await-holding-invalid-types) | `[]` | +| [max-include-file-size](#max-include-file-size) | `1000000` | +| [allow-expect-in-tests](#allow-expect-in-tests) | `false` | +| [allow-unwrap-in-tests](#allow-unwrap-in-tests) | `false` | +| [allow-dbg-in-tests](#allow-dbg-in-tests) | `false` | +| [allow-print-in-tests](#allow-print-in-tests) | `false` | +| [large-error-threshold](#large-error-threshold) | `128` | +| [ignore-interior-mutability](#ignore-interior-mutability) | `["bytes::Bytes"]` | +| [allow-mixed-uninlined-format-args](#allow-mixed-uninlined-format-args) | `true` | +| [suppress-restriction-lint-in-const](#suppress-restriction-lint-in-const) | `false` | +| [missing-docs-in-crate-items](#missing-docs-in-crate-items) | `false` | + +### arithmetic-side-effects-allowed +Suppress checking of the passed type names in all types of operations. + +If a specific operation is desired, consider using `arithmetic_side_effects_allowed_binary` or `arithmetic_side_effects_allowed_unary` instead. + +#### Example + +```toml +arithmetic-side-effects-allowed = ["SomeType", "AnotherType"] +``` + +#### Noteworthy + +A type, say `SomeType`, listed in this configuration has the same behavior of +`["SomeType" , "*"], ["*", "SomeType"]` in `arithmetic_side_effects_allowed_binary`. + +**Default Value:** `{}` (`rustc_data_structures::fx::FxHashSet<String>`) + +* [arithmetic_side_effects](https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects) + + +### arithmetic-side-effects-allowed-binary +Suppress checking of the passed type pair names in binary operations like addition or +multiplication. + +Supports the "*" wildcard to indicate that a certain type won't trigger the lint regardless +of the involved counterpart. For example, `["SomeType", "*"]` or `["*", "AnotherType"]`. + +Pairs are asymmetric, which means that `["SomeType", "AnotherType"]` is not the same as +`["AnotherType", "SomeType"]`. + +#### Example + +```toml +arithmetic-side-effects-allowed-binary = [["SomeType" , "f32"], ["AnotherType", "*"]] +``` + +**Default Value:** `[]` (`Vec<[String; 2]>`) + +* [arithmetic_side_effects](https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects) + + +### arithmetic-side-effects-allowed-unary +Suppress checking of the passed type names in unary operations like "negation" (`-`). + +#### Example + +```toml +arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"] +``` + +**Default Value:** `{}` (`rustc_data_structures::fx::FxHashSet<String>`) + +* [arithmetic_side_effects](https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects) + + +### avoid-breaking-exported-api +Suppress lints whenever the suggested change would cause breakage for other crates. + +**Default Value:** `true` (`bool`) + +* [enum_variant_names](https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names) +* [large_types_passed_by_value](https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value) +* [trivially_copy_pass_by_ref](https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref) +* [unnecessary_wraps](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_wraps) +* [unused_self](https://rust-lang.github.io/rust-clippy/master/index.html#unused_self) +* [upper_case_acronyms](https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms) +* [wrong_self_convention](https://rust-lang.github.io/rust-clippy/master/index.html#wrong_self_convention) +* [box_collection](https://rust-lang.github.io/rust-clippy/master/index.html#box_collection) +* [redundant_allocation](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation) +* [rc_buffer](https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer) +* [vec_box](https://rust-lang.github.io/rust-clippy/master/index.html#vec_box) +* [option_option](https://rust-lang.github.io/rust-clippy/master/index.html#option_option) +* [linkedlist](https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist) +* [rc_mutex](https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex) + + +### msrv +The minimum rust version that the project supports + +**Default Value:** `None` (`Option<String>`) + +* [manual_split_once](https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once) +* [manual_str_repeat](https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat) +* [cloned_instead_of_copied](https://rust-lang.github.io/rust-clippy/master/index.html#cloned_instead_of_copied) +* [redundant_field_names](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names) +* [redundant_static_lifetimes](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes) +* [filter_map_next](https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_next) +* [checked_conversions](https://rust-lang.github.io/rust-clippy/master/index.html#checked_conversions) +* [manual_range_contains](https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains) +* [use_self](https://rust-lang.github.io/rust-clippy/master/index.html#use_self) +* [mem_replace_with_default](https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default) +* [manual_non_exhaustive](https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive) +* [option_as_ref_deref](https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref) +* [map_unwrap_or](https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or) +* [match_like_matches_macro](https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro) +* [manual_strip](https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip) +* [missing_const_for_fn](https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn) +* [unnested_or_patterns](https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns) +* [from_over_into](https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into) +* [ptr_as_ptr](https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr) +* [if_then_some_else_none](https://rust-lang.github.io/rust-clippy/master/index.html#if_then_some_else_none) +* [approx_constant](https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant) +* [deprecated_cfg_attr](https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr) +* [index_refutable_slice](https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice) +* [map_clone](https://rust-lang.github.io/rust-clippy/master/index.html#map_clone) +* [borrow_as_ptr](https://rust-lang.github.io/rust-clippy/master/index.html#borrow_as_ptr) +* [manual_bits](https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits) +* [err_expect](https://rust-lang.github.io/rust-clippy/master/index.html#err_expect) +* [cast_abs_to_unsigned](https://rust-lang.github.io/rust-clippy/master/index.html#cast_abs_to_unsigned) +* [uninlined_format_args](https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args) +* [manual_clamp](https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp) +* [manual_let_else](https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else) +* [unchecked_duration_subtraction](https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction) +* [collapsible_str_replace](https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_str_replace) +* [seek_from_current](https://rust-lang.github.io/rust-clippy/master/index.html#seek_from_current) +* [seek_rewind](https://rust-lang.github.io/rust-clippy/master/index.html#seek_rewind) +* [unnecessary_lazy_evaluations](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations) +* [transmute_ptr_to_ref](https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref) +* [almost_complete_range](https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_range) +* [needless_borrow](https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow) +* [derivable_impls](https://rust-lang.github.io/rust-clippy/master/index.html#derivable_impls) +* [manual_is_ascii_check](https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check) +* [manual_rem_euclid](https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid) +* [manual_retain](https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain) + + +### cognitive-complexity-threshold +The maximum cognitive complexity a function can have + +**Default Value:** `25` (`u64`) + +* [cognitive_complexity](https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity) + + +### disallowed-names +The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value +`".."` can be used as part of the list to indicate, that the configured values should be appended to the +default configuration of Clippy. By default any configuration will replace the default value. + +**Default Value:** `["foo", "baz", "quux"]` (`Vec<String>`) + +* [disallowed_names](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names) + + +### doc-valid-idents +The list of words this lint should not consider as identifiers needing ticks. The value +`".."` can be used as part of the list to indicate, that the configured values should be appended to the +default configuration of Clippy. By default any configuraction will replace the default value. For example: +* `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. +* `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. + +Default list: + +**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", "WebGL", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` (`Vec<String>`) + +* [doc_markdown](https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown) + + +### too-many-arguments-threshold +The maximum number of argument a function or method can have + +**Default Value:** `7` (`u64`) + +* [too_many_arguments](https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments) + + +### type-complexity-threshold +The maximum complexity a type can have + +**Default Value:** `250` (`u64`) + +* [type_complexity](https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity) + + +### single-char-binding-names-threshold +The maximum number of single char bindings a scope may have + +**Default Value:** `4` (`u64`) + +* [many_single_char_names](https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names) + + +### too-large-for-stack +The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap + +**Default Value:** `200` (`u64`) + +* [boxed_local](https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local) +* [useless_vec](https://rust-lang.github.io/rust-clippy/master/index.html#useless_vec) + + +### enum-variant-name-threshold +The minimum number of enum variants for the lints about variant names to trigger + +**Default Value:** `3` (`u64`) + +* [enum_variant_names](https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names) + + +### enum-variant-size-threshold +The maximum size of an enum's variant to avoid box suggestion + +**Default Value:** `200` (`u64`) + +* [large_enum_variant](https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant) + + +### verbose-bit-mask-threshold +The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros' + +**Default Value:** `1` (`u64`) + +* [verbose_bit_mask](https://rust-lang.github.io/rust-clippy/master/index.html#verbose_bit_mask) + + +### literal-representation-threshold +The lower bound for linting decimal literals + +**Default Value:** `16384` (`u64`) + +* [decimal_literal_representation](https://rust-lang.github.io/rust-clippy/master/index.html#decimal_literal_representation) + + +### trivial-copy-size-limit +The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference. + +**Default Value:** `None` (`Option<u64>`) + +* [trivially_copy_pass_by_ref](https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref) + + +### pass-by-value-size-limit +The minimum size (in bytes) to consider a type for passing by reference instead of by value. + +**Default Value:** `256` (`u64`) + +* [large_types_passed_by_value](https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value) + + +### too-many-lines-threshold +The maximum number of lines a function or method can have + +**Default Value:** `100` (`u64`) + +* [too_many_lines](https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines) + + +### array-size-threshold +The maximum allowed size for arrays on the stack + +**Default Value:** `512000` (`u64`) + +* [large_stack_arrays](https://rust-lang.github.io/rust-clippy/master/index.html#large_stack_arrays) +* [large_const_arrays](https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays) + + +### vec-box-size-threshold +The size of the boxed type in bytes, where boxing in a `Vec` is allowed + +**Default Value:** `4096` (`u64`) + +* [vec_box](https://rust-lang.github.io/rust-clippy/master/index.html#vec_box) + + +### max-trait-bounds +The maximum number of bounds a trait can have to be linted + +**Default Value:** `3` (`u64`) + +* [type_repetition_in_bounds](https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds) + + +### max-struct-bools +The maximum number of bool fields a struct can have + +**Default Value:** `3` (`u64`) + +* [struct_excessive_bools](https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools) + + +### max-fn-params-bools +The maximum number of bool parameters a function can have + +**Default Value:** `3` (`u64`) + +* [fn_params_excessive_bools](https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools) + + +### warn-on-all-wildcard-imports +Whether to allow certain wildcard imports (prelude, super in tests). + +**Default Value:** `false` (`bool`) + +* [wildcard_imports](https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports) + + +### disallowed-macros +The list of disallowed macros, written as fully qualified paths. + +**Default Value:** `[]` (`Vec<crate::utils::conf::DisallowedPath>`) + +* [disallowed_macros](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_macros) + + +### disallowed-methods +The list of disallowed methods, written as fully qualified paths. + +**Default Value:** `[]` (`Vec<crate::utils::conf::DisallowedPath>`) + +* [disallowed_methods](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods) + + +### disallowed-types +The list of disallowed types, written as fully qualified paths. + +**Default Value:** `[]` (`Vec<crate::utils::conf::DisallowedPath>`) + +* [disallowed_types](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types) + + +### unreadable-literal-lint-fractions +Should the fraction of a decimal be linted to include separators. + +**Default Value:** `true` (`bool`) + +* [unreadable_literal](https://rust-lang.github.io/rust-clippy/master/index.html#unreadable_literal) + + +### upper-case-acronyms-aggressive +Enables verbose mode. Triggers if there is more than one uppercase char next to each other + +**Default Value:** `false` (`bool`) + +* [upper_case_acronyms](https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms) + + +### matches-for-let-else +Whether the matches should be considered by the lint, and whether there should +be filtering for common types. + +**Default Value:** `WellKnownTypes` (`crate::manual_let_else::MatchLintBehaviour`) + +* [manual_let_else](https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else) + + +### cargo-ignore-publish +For internal testing only, ignores the current `publish` settings in the Cargo manifest. + +**Default Value:** `false` (`bool`) + +* [_cargo_common_metadata](https://rust-lang.github.io/rust-clippy/master/index.html#_cargo_common_metadata) + + +### standard-macro-braces +Enforce the named macros always use the braces specified. + +A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro +is could be used with a full path two `MacroMatcher`s have to be added one with the full path +`crate_name::macro_name` and one with just the macro name. + +**Default Value:** `[]` (`Vec<crate::nonstandard_macro_braces::MacroMatcher>`) + +* [nonstandard_macro_braces](https://rust-lang.github.io/rust-clippy/master/index.html#nonstandard_macro_braces) + + +### enforced-import-renames +The list of imports to always rename, a fully qualified path followed by the rename. + +**Default Value:** `[]` (`Vec<crate::utils::conf::Rename>`) + +* [missing_enforced_import_renames](https://rust-lang.github.io/rust-clippy/master/index.html#missing_enforced_import_renames) + + +### allowed-scripts +The list of unicode scripts allowed to be used in the scope. + +**Default Value:** `["Latin"]` (`Vec<String>`) + +* [disallowed_script_idents](https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_script_idents) + + +### enable-raw-pointer-heuristic-for-send +Whether to apply the raw pointer heuristic to determine if a type is `Send`. + +**Default Value:** `true` (`bool`) + +* [non_send_fields_in_send_ty](https://rust-lang.github.io/rust-clippy/master/index.html#non_send_fields_in_send_ty) + + +### max-suggested-slice-pattern-length +When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in +the slice pattern that is suggested. If more elements would be necessary, the lint is suppressed. +For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements. + +**Default Value:** `3` (`u64`) + +* [index_refutable_slice](https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice) + + +### await-holding-invalid-types + + +**Default Value:** `[]` (`Vec<crate::utils::conf::DisallowedPath>`) + +* [await_holding_invalid_type](https://rust-lang.github.io/rust-clippy/master/index.html#await_holding_invalid_type) + + +### max-include-file-size +The maximum size of a file included via `include_bytes!()` or `include_str!()`, in bytes + +**Default Value:** `1000000` (`u64`) + +* [large_include_file](https://rust-lang.github.io/rust-clippy/master/index.html#large_include_file) + + +### allow-expect-in-tests +Whether `expect` should be allowed in test functions or `#[cfg(test)]` + +**Default Value:** `false` (`bool`) + +* [expect_used](https://rust-lang.github.io/rust-clippy/master/index.html#expect_used) + + +### allow-unwrap-in-tests +Whether `unwrap` should be allowed in test functions or `#[cfg(test)]` + +**Default Value:** `false` (`bool`) + +* [unwrap_used](https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used) + + +### allow-dbg-in-tests +Whether `dbg!` should be allowed in test functions or `#[cfg(test)]` + +**Default Value:** `false` (`bool`) + +* [dbg_macro](https://rust-lang.github.io/rust-clippy/master/index.html#dbg_macro) + + +### allow-print-in-tests +Whether print macros (ex. `println!`) should be allowed in test functions or `#[cfg(test)]` + +**Default Value:** `false` (`bool`) + +* [print_stdout](https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout) +* [print_stderr](https://rust-lang.github.io/rust-clippy/master/index.html#print_stderr) + + +### large-error-threshold +The maximum size of the `Err`-variant in a `Result` returned from a function + +**Default Value:** `128` (`u64`) + +* [result_large_err](https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err) + + +### ignore-interior-mutability +A list of paths to types that should be treated like `Arc`, i.e. ignored but +for the generic parameters for determining interior mutability + +**Default Value:** `["bytes::Bytes"]` (`Vec<String>`) + +* [mutable_key_type](https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type) + + +### allow-mixed-uninlined-format-args +Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)` + +**Default Value:** `true` (`bool`) + +* [uninlined_format_args](https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args) + + +### suppress-restriction-lint-in-const +Whether to suppress a restriction lint in constant code. In same +cases the restructured operation might not be unavoidable, as the +suggested counterparts are unavailable in constant code. This +configuration will cause restriction lints to trigger even +if no suggestion can be made. + +**Default Value:** `false` (`bool`) + +* [indexing_slicing](https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing) + + +### missing-docs-in-crate-items +Whether to **only** check for missing documentation in items visible within the current +crate. For example, `pub(crate)` items. + +**Default Value:** `false` (`bool`) + +* [missing_docs_in_private_items](https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items) + + + diff --git a/src/tools/clippy/clippy_dev/Cargo.toml b/src/tools/clippy/clippy_dev/Cargo.toml index 510c7e852..c3f8a782d 100644 --- a/src/tools/clippy/clippy_dev/Cargo.toml +++ b/src/tools/clippy/clippy_dev/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] aho-corasick = "0.7" -clap = "3.2" +clap = "4.1.4" indoc = "1.0" itertools = "0.10.1" opener = "0.5" diff --git a/src/tools/clippy/clippy_dev/src/main.rs b/src/tools/clippy/clippy_dev/src/main.rs index d3e036692..e2457e5a8 100644 --- a/src/tools/clippy/clippy_dev/src/main.rs +++ b/src/tools/clippy/clippy_dev/src/main.rs @@ -2,7 +2,7 @@ // warn on lints, that are included in `rust-lang/rust`s bootstrap #![warn(rust_2018_idioms, unused_lifetimes)] -use clap::{Arg, ArgAction, ArgMatches, Command, PossibleValue}; +use clap::{Arg, ArgAction, ArgMatches, Command}; use clippy_dev::{bless, dogfood, fmt, lint, new_lint, serve, setup, update_lints}; use indoc::indoc; @@ -11,22 +11,22 @@ fn main() { match matches.subcommand() { Some(("bless", matches)) => { - bless::bless(matches.contains_id("ignore-timestamp")); + bless::bless(matches.get_flag("ignore-timestamp")); }, Some(("dogfood", matches)) => { dogfood::dogfood( - matches.contains_id("fix"), - matches.contains_id("allow-dirty"), - matches.contains_id("allow-staged"), + matches.get_flag("fix"), + matches.get_flag("allow-dirty"), + matches.get_flag("allow-staged"), ); }, Some(("fmt", matches)) => { - fmt::run(matches.contains_id("check"), matches.contains_id("verbose")); + fmt::run(matches.get_flag("check"), matches.get_flag("verbose")); }, Some(("update_lints", matches)) => { - if matches.contains_id("print-only") { + if matches.get_flag("print-only") { update_lints::print_lints(); - } else if matches.contains_id("check") { + } else if matches.get_flag("check") { update_lints::update(update_lints::UpdateMode::Check); } else { update_lints::update(update_lints::UpdateMode::Change); @@ -38,7 +38,7 @@ fn main() { matches.get_one::<String>("name"), matches.get_one::<String>("category").map(String::as_str), matches.get_one::<String>("type").map(String::as_str), - matches.contains_id("msrv"), + matches.get_flag("msrv"), ) { Ok(_) => update_lints::update(update_lints::UpdateMode::Change), Err(e) => eprintln!("Unable to create lint: {e}"), @@ -46,7 +46,7 @@ fn main() { }, Some(("setup", sub_command)) => match sub_command.subcommand() { Some(("intellij", matches)) => { - if matches.contains_id("remove") { + if matches.get_flag("remove") { setup::intellij::remove_rustc_src(); } else { setup::intellij::setup_rustc_src( @@ -57,17 +57,17 @@ fn main() { } }, Some(("git-hook", matches)) => { - if matches.contains_id("remove") { + if matches.get_flag("remove") { setup::git_hook::remove_hook(); } else { - setup::git_hook::install_hook(matches.contains_id("force-override")); + setup::git_hook::install_hook(matches.get_flag("force-override")); } }, Some(("vscode-tasks", matches)) => { - if matches.contains_id("remove") { + if matches.get_flag("remove") { setup::vscode::remove_tasks(); } else { - setup::vscode::install_tasks(matches.contains_id("force-override")); + setup::vscode::install_tasks(matches.get_flag("force-override")); } }, _ => {}, @@ -91,7 +91,7 @@ fn main() { Some(("rename_lint", matches)) => { let old_name = matches.get_one::<String>("old_name").unwrap(); let new_name = matches.get_one::<String>("new_name").unwrap_or(old_name); - let uplift = matches.contains_id("uplift"); + let uplift = matches.get_flag("uplift"); update_lints::rename(old_name, new_name, uplift); }, Some(("deprecate", matches)) => { @@ -110,24 +110,37 @@ fn get_clap_config() -> ArgMatches { Command::new("bless").about("bless the test output changes").arg( Arg::new("ignore-timestamp") .long("ignore-timestamp") + .action(ArgAction::SetTrue) .help("Include files updated before clippy was built"), ), Command::new("dogfood").about("Runs the dogfood test").args([ - Arg::new("fix").long("fix").help("Apply the suggestions when possible"), + Arg::new("fix") + .long("fix") + .action(ArgAction::SetTrue) + .help("Apply the suggestions when possible"), Arg::new("allow-dirty") .long("allow-dirty") + .action(ArgAction::SetTrue) .help("Fix code even if the working directory has changes") .requires("fix"), Arg::new("allow-staged") .long("allow-staged") + .action(ArgAction::SetTrue) .help("Fix code even if the working directory has staged changes") .requires("fix"), ]), Command::new("fmt") .about("Run rustfmt on all projects and tests") .args([ - Arg::new("check").long("check").help("Use the rustfmt --check option"), - Arg::new("verbose").short('v').long("verbose").help("Echo commands run"), + Arg::new("check") + .long("check") + .action(ArgAction::SetTrue) + .help("Use the rustfmt --check option"), + Arg::new("verbose") + .short('v') + .long("verbose") + .action(ArgAction::SetTrue) + .help("Echo commands run"), ]), Command::new("update_lints") .about("Updates lint registration and information from the source code") @@ -140,13 +153,17 @@ fn get_clap_config() -> ArgMatches { * all lints are registered in the lint store", ) .args([ - Arg::new("print-only").long("print-only").help( - "Print a table of lints to STDOUT. \ - This does not include deprecated and internal lints. \ - (Does not modify any files)", - ), + Arg::new("print-only") + .long("print-only") + .action(ArgAction::SetTrue) + .help( + "Print a table of lints to STDOUT. \ + This does not include deprecated and internal lints. \ + (Does not modify any files)", + ), Arg::new("check") .long("check") + .action(ArgAction::SetTrue) .help("Checks that `cargo dev update_lints` has been run. Used on CI."), ]), Command::new("new_lint") @@ -156,15 +173,13 @@ fn get_clap_config() -> ArgMatches { .short('p') .long("pass") .help("Specify whether the lint runs during the early or late pass") - .takes_value(true) - .value_parser([PossibleValue::new("early"), PossibleValue::new("late")]) + .value_parser(["early", "late"]) .conflicts_with("type") .required_unless_present("type"), Arg::new("name") .short('n') .long("name") .help("Name of the new lint in snake case, ex: fn_too_long") - .takes_value(true) .required(true), Arg::new("category") .short('c') @@ -172,25 +187,23 @@ fn get_clap_config() -> ArgMatches { .help("What category the lint belongs to") .default_value("nursery") .value_parser([ - PossibleValue::new("style"), - PossibleValue::new("correctness"), - PossibleValue::new("suspicious"), - PossibleValue::new("complexity"), - PossibleValue::new("perf"), - PossibleValue::new("pedantic"), - PossibleValue::new("restriction"), - PossibleValue::new("cargo"), - PossibleValue::new("nursery"), - PossibleValue::new("internal"), - PossibleValue::new("internal_warn"), - ]) - .takes_value(true), - Arg::new("type") - .long("type") - .help("What directory the lint belongs in") - .takes_value(true) - .required(false), - Arg::new("msrv").long("msrv").help("Add MSRV config code to the lint"), + "style", + "correctness", + "suspicious", + "complexity", + "perf", + "pedantic", + "restriction", + "cargo", + "nursery", + "internal", + "internal_warn", + ]), + Arg::new("type").long("type").help("What directory the lint belongs in"), + Arg::new("msrv") + .long("msrv") + .action(ArgAction::SetTrue) + .help("Add MSRV config code to the lint"), ]), Command::new("setup") .about("Support for setting up your personal development environment") @@ -201,13 +214,12 @@ fn get_clap_config() -> ArgMatches { .args([ Arg::new("remove") .long("remove") - .help("Remove the dependencies added with 'cargo dev setup intellij'") - .required(false), + .action(ArgAction::SetTrue) + .help("Remove the dependencies added with 'cargo dev setup intellij'"), Arg::new("rustc-repo-path") .long("repo-path") .short('r') .help("The path to a rustc repo that will be used for setting the dependencies") - .takes_value(true) .value_name("path") .conflicts_with("remove") .required(true), @@ -217,26 +229,26 @@ fn get_clap_config() -> ArgMatches { .args([ Arg::new("remove") .long("remove") - .help("Remove the pre-commit hook added with 'cargo dev setup git-hook'") - .required(false), + .action(ArgAction::SetTrue) + .help("Remove the pre-commit hook added with 'cargo dev setup git-hook'"), Arg::new("force-override") .long("force-override") .short('f') - .help("Forces the override of an existing git pre-commit hook") - .required(false), + .action(ArgAction::SetTrue) + .help("Forces the override of an existing git pre-commit hook"), ]), Command::new("vscode-tasks") .about("Add several tasks to vscode for formatting, validation and testing") .args([ Arg::new("remove") .long("remove") - .help("Remove the tasks added with 'cargo dev setup vscode-tasks'") - .required(false), + .action(ArgAction::SetTrue) + .help("Remove the tasks added with 'cargo dev setup vscode-tasks'"), Arg::new("force-override") .long("force-override") .short('f') - .help("Forces the override of existing vscode tasks") - .required(false), + .action(ArgAction::SetTrue) + .help("Forces the override of existing vscode tasks"), ]), ]), Command::new("remove") @@ -295,6 +307,7 @@ fn get_clap_config() -> ArgMatches { .help("The new name of the lint"), Arg::new("uplift") .long("uplift") + .action(ArgAction::SetTrue) .help("This lint will be uplifted into rustc"), ]), Command::new("deprecate").about("Deprecates the given lint").args([ @@ -305,8 +318,6 @@ fn get_clap_config() -> ArgMatches { Arg::new("reason") .long("reason") .short('r') - .required(false) - .takes_value(true) .help("The reason for deprecation"), ]), ]) diff --git a/src/tools/clippy/clippy_dev/src/new_lint.rs b/src/tools/clippy/clippy_dev/src/new_lint.rs index ec7f1dd0d..420214d92 100644 --- a/src/tools/clippy/clippy_dev/src/new_lint.rs +++ b/src/tools/clippy/clippy_dev/src/new_lint.rs @@ -1,5 +1,6 @@ use crate::clippy_project_root; use indoc::{formatdoc, writedoc}; +use std::fmt; use std::fmt::Write as _; use std::fs::{self, OpenOptions}; use std::io::prelude::*; @@ -256,7 +257,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { ) }); - let _ = write!(result, "{}", get_lint_declaration(&name_upper, category)); + let _: fmt::Result = write!(result, "{}", get_lint_declaration(&name_upper, category)); result.push_str(&if enable_msrv { formatdoc!( @@ -353,7 +354,7 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R let mut lint_file_contents = String::new(); if enable_msrv { - let _ = writedoc!( + let _: fmt::Result = writedoc!( lint_file_contents, r#" use clippy_utils::msrvs::{{self, Msrv}}; @@ -373,7 +374,7 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R name_upper = name_upper, ); } else { - let _ = writedoc!( + let _: fmt::Result = writedoc!( lint_file_contents, r#" use rustc_lint::{{{context_import}, LintContext}}; @@ -521,7 +522,7 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str> .chain(std::iter::once(&*lint_name_upper)) .filter(|s| !s.is_empty()) { - let _ = write!(new_arr_content, "\n {ident},"); + let _: fmt::Result = write!(new_arr_content, "\n {ident},"); } new_arr_content.push('\n'); diff --git a/src/tools/clippy/clippy_dev/src/update_lints.rs b/src/tools/clippy/clippy_dev/src/update_lints.rs index 837618c92..779e4d0e1 100644 --- a/src/tools/clippy/clippy_dev/src/update_lints.rs +++ b/src/tools/clippy/clippy_dev/src/update_lints.rs @@ -5,7 +5,7 @@ use itertools::Itertools; use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind}; use std::collections::{HashMap, HashSet}; use std::ffi::OsStr; -use std::fmt::Write; +use std::fmt::{self, Write}; use std::fs::{self, OpenOptions}; use std::io::{self, Read, Seek, SeekFrom, Write as _}; use std::ops::Range; @@ -691,7 +691,7 @@ fn gen_deprecated(lints: &[DeprecatedLint]) -> String { let mut output = GENERATED_FILE_COMMENT.to_string(); output.push_str("{\n"); for lint in lints { - let _ = write!( + let _: fmt::Result = write!( output, concat!( " store.register_removed(\n", @@ -726,7 +726,7 @@ fn gen_declared_lints<'a>( if !is_public { output.push_str(" #[cfg(feature = \"internal\")]\n"); } - let _ = writeln!(output, " crate::{module_name}::{lint_name}_INFO,"); + let _: fmt::Result = writeln!(output, " crate::{module_name}::{lint_name}_INFO,"); } output.push_str("];\n"); diff --git a/src/tools/clippy/clippy_lints/Cargo.toml b/src/tools/clippy/clippy_lints/Cargo.toml index a9f69b1ba..796f1ff16 100644 --- a/src/tools/clippy/clippy_lints/Cargo.toml +++ b/src/tools/clippy/clippy_lints/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_lints" -version = "0.1.68" +version = "0.1.69" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" @@ -9,7 +9,7 @@ keywords = ["clippy", "lint", "plugin"] edition = "2021" [dependencies] -cargo_metadata = "0.14" +cargo_metadata = "0.15.3" clippy_utils = { path = "../clippy_utils" } declare_clippy_lint = { path = "../declare_clippy_lint" } if_chain = "1.0" diff --git a/src/tools/clippy/clippy_lints/src/bool_assert_comparison.rs b/src/tools/clippy/clippy_lints/src/bool_assert_comparison.rs index 82d368bb8..1d9096ea6 100644 --- a/src/tools/clippy/clippy_lints/src/bool_assert_comparison.rs +++ b/src/tools/clippy/clippy_lints/src/bool_assert_comparison.rs @@ -1,10 +1,12 @@ +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node}; -use clippy_utils::{diagnostics::span_lint_and_sugg, ty::implements_trait}; +use clippy_utils::sugg::Sugg; +use clippy_utils::ty::{implements_trait, is_copy}; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, Lit}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::Ident; @@ -33,19 +35,19 @@ declare_clippy_lint! { declare_lint_pass!(BoolAssertComparison => [BOOL_ASSERT_COMPARISON]); -fn is_bool_lit(e: &Expr<'_>) -> bool { - matches!( - e.kind, - ExprKind::Lit(Lit { - node: LitKind::Bool(_), - .. - }) - ) && !e.span.from_expansion() +fn extract_bool_lit(e: &Expr<'_>) -> Option<bool> { + if let ExprKind::Lit(Lit { + node: LitKind::Bool(b), .. + }) = e.kind + && !e.span.from_expansion() + { + Some(b) + } else { + None + } } -fn is_impl_not_trait_with_bool_out(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { - let ty = cx.typeck_results().expr_ty(e); - +fn is_impl_not_trait_with_bool_out<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { cx.tcx .lang_items() .not_trait() @@ -70,38 +72,70 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return }; let macro_name = cx.tcx.item_name(macro_call.def_id); - if !matches!( - macro_name.as_str(), - "assert_eq" | "debug_assert_eq" | "assert_ne" | "debug_assert_ne" - ) { - return; - } + let eq_macro = match macro_name.as_str() { + "assert_eq" | "debug_assert_eq" => true, + "assert_ne" | "debug_assert_ne" => false, + _ => return, + }; let Some ((a, b, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else { return }; - if !(is_bool_lit(a) ^ is_bool_lit(b)) { + + let a_span = a.span.source_callsite(); + let b_span = b.span.source_callsite(); + + let (lit_span, bool_value, non_lit_expr) = match (extract_bool_lit(a), extract_bool_lit(b)) { + // assert_eq!(true/false, b) + // ^^^^^^^^^^^^ + (Some(bool_value), None) => (a_span.until(b_span), bool_value, b), + // assert_eq!(a, true/false) + // ^^^^^^^^^^^^ + (None, Some(bool_value)) => (b_span.with_lo(a_span.hi()), bool_value, a), // If there are two boolean arguments, we definitely don't understand // what's going on, so better leave things as is... // // Or there is simply no boolean and then we can leave things as is! - return; - } + _ => return, + }; + + let non_lit_ty = cx.typeck_results().expr_ty(non_lit_expr); - if !is_impl_not_trait_with_bool_out(cx, a) || !is_impl_not_trait_with_bool_out(cx, b) { + if !is_impl_not_trait_with_bool_out(cx, non_lit_ty) { // At this point the expression which is not a boolean // literal does not implement Not trait with a bool output, // so we cannot suggest to rewrite our code return; } + if !is_copy(cx, non_lit_ty) { + // Only lint with types that are `Copy` because `assert!(x)` takes + // ownership of `x` whereas `assert_eq(x, true)` does not + return; + } + let macro_name = macro_name.as_str(); let non_eq_mac = ¯o_name[..macro_name.len() - 3]; - span_lint_and_sugg( + span_lint_and_then( cx, BOOL_ASSERT_COMPARISON, macro_call.span, &format!("used `{macro_name}!` with a literal bool"), - "replace it with", - format!("{non_eq_mac}!(..)"), - Applicability::MaybeIncorrect, + |diag| { + // assert_eq!(...) + // ^^^^^^^^^ + let name_span = cx.sess().source_map().span_until_char(macro_call.span, '!'); + + let mut suggestions = vec![(name_span, non_eq_mac.to_string()), (lit_span, String::new())]; + + if bool_value ^ eq_macro { + let Some(sugg) = Sugg::hir_opt(cx, non_lit_expr) else { return }; + suggestions.push((non_lit_expr.span, (!sugg).to_string())); + } + + diag.multipart_suggestion( + format!("replace it with `{non_eq_mac}!(..)`"), + suggestions, + Applicability::MachineApplicable, + ); + }, ); } } diff --git a/src/tools/clippy/clippy_lints/src/booleans.rs b/src/tools/clippy/clippy_lints/src/booleans.rs index 939bdbcdc..e8106beec 100644 --- a/src/tools/clippy/clippy_lints/src/booleans.rs +++ b/src/tools/clippy/clippy_lints/src/booleans.rs @@ -6,9 +6,10 @@ use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; -use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, UnOp}; +use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Span; use rustc_span::sym; @@ -82,7 +83,7 @@ impl<'tcx> LateLintPass<'tcx> for NonminimalBool { _: &'tcx FnDecl<'_>, body: &'tcx Body<'_>, _: Span, - _: HirId, + _: LocalDefId, ) { NonminimalBoolVisitor { cx }.visit_body(body); } diff --git a/src/tools/clippy/clippy_lints/src/box_default.rs b/src/tools/clippy/clippy_lints/src/box_default.rs index 9d98a6bab..dfa949d1a 100644 --- a/src/tools/clippy/clippy_lints/src/box_default.rs +++ b/src/tools/clippy/clippy_lints/src/box_default.rs @@ -117,7 +117,8 @@ fn given_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { ) => { if let Some(index) = args.iter().position(|arg| arg.hir_id == expr.hir_id) && let Some(sig) = expr_sig(cx, path) && - let Some(input) = sig.input(index) + let Some(input) = sig.input(index) && + !cx.typeck_results().expr_ty_adjusted(expr).boxed_ty().is_trait() { input.no_bound_vars().is_some() } else { diff --git a/src/tools/clippy/clippy_lints/src/casts/as_ptr_cast_mut.rs b/src/tools/clippy/clippy_lints/src/casts/as_ptr_cast_mut.rs index 9409f4844..1633ffd58 100644 --- a/src/tools/clippy/clippy_lints/src/casts/as_ptr_cast_mut.rs +++ b/src/tools/clippy/clippy_lints/src/casts/as_ptr_cast_mut.rs @@ -17,7 +17,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, && let ExprKind::MethodCall(method_name, receiver, [], _) = cast_expr.peel_blocks().kind && method_name.ident.name == rustc_span::sym::as_ptr && let Some(as_ptr_did) = cx.typeck_results().type_dependent_def_id(cast_expr.peel_blocks().hir_id) - && let as_ptr_sig = cx.tcx.fn_sig(as_ptr_did) + && let as_ptr_sig = cx.tcx.fn_sig(as_ptr_did).subst_identity() && let Some(first_param_ty) = as_ptr_sig.skip_binder().inputs().iter().next() && let ty::Ref(_, _, Mutability::Not) = first_param_ty.kind() && let Some(recv) = snippet_opt(cx, receiver.span) diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs b/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs index a63764849..823970e35 100644 --- a/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs @@ -1,11 +1,14 @@ use clippy_utils::consts::{constant, Constant}; -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::expr_or_init; +use clippy_utils::source::snippet; use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize}; +use rustc_errors::{Applicability, SuggestionStyle}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; use rustc_middle::ty::{self, FloatTy, Ty}; +use rustc_span::Span; use rustc_target::abi::IntegerType; use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION}; @@ -74,7 +77,14 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b } } -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { +pub(super) fn check( + cx: &LateContext<'_>, + expr: &Expr<'_>, + cast_expr: &Expr<'_>, + cast_from: Ty<'_>, + cast_to: Ty<'_>, + cast_to_span: Span, +) { let msg = match (cast_from.kind(), cast_to.is_integral()) { (ty::Int(_) | ty::Uint(_), true) => { let from_nbits = apply_reductions( @@ -139,7 +149,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, ); return; } - format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}",) + format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}") }, (ty::Float(_), true) => { @@ -153,5 +163,19 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, _ => return, }; - span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg); + let name_of_cast_from = snippet(cx, cast_expr.span, ".."); + let cast_to_snip = snippet(cx, cast_to_span, ".."); + let suggestion = format!("{cast_to_snip}::try_from({name_of_cast_from})"); + + span_lint_and_then(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg, |diag| { + diag.help("if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ..."); + diag.span_suggestion_with_style( + expr.span, + "... or use `try_from` and handle the error accordingly", + suggestion, + Applicability::Unspecified, + // always show the suggestion in a separate line + SuggestionStyle::ShowAlways, + ); + }); } diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs b/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs index 97054a0d1..6c8ee296c 100644 --- a/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -66,7 +66,7 @@ fn is_used_as_unaligned(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { if matches!(name.ident.as_str(), "read_unaligned" | "write_unaligned") && let Some(def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id) && let Some(def_id) = cx.tcx.impl_of_method(def_id) - && cx.tcx.type_of(def_id).is_unsafe_ptr() + && cx.tcx.type_of(def_id).subst_identity().is_unsafe_ptr() { true } else { diff --git a/src/tools/clippy/clippy_lints/src/casts/mod.rs b/src/tools/clippy/clippy_lints/src/casts/mod.rs index 161e3a698..362f70d12 100644 --- a/src/tools/clippy/clippy_lints/src/casts/mod.rs +++ b/src/tools/clippy/clippy_lints/src/casts/mod.rs @@ -80,7 +80,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for casts between numerical types that may /// truncate large values. This is expected behavior, so the cast is `Allow` by - /// default. + /// default. It suggests user either explicitly ignore the lint, + /// or use `try_from()` and handle the truncation, default, or panic explicitly. /// /// ### Why is this bad? /// In some problem domains, it is good practice to avoid @@ -93,6 +94,21 @@ declare_clippy_lint! { /// x as u8 /// } /// ``` + /// Use instead: + /// ``` + /// fn as_u8(x: u64) -> u8 { + /// if let Ok(x) = u8::try_from(x) { + /// x + /// } else { + /// todo!(); + /// } + /// } + /// // Or + /// #[allow(clippy::cast_possible_truncation)] + /// fn as_u16(x: u64) -> u16 { + /// x as u16 + /// } + /// ``` #[clippy::version = "pre 1.29.0"] pub CAST_POSSIBLE_TRUNCATION, pedantic, @@ -712,7 +728,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts { fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to); if cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) { - cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to); + cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to, cast_to_hir.span); if cast_from.is_numeric() { cast_possible_wrap::check(cx, expr, cast_from, cast_to); cast_precision_loss::check(cx, expr, cast_from, cast_to); diff --git a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs index 1c3a89a97..e8531157e 100644 --- a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs +++ b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs @@ -8,9 +8,10 @@ use clippy_utils::{get_async_fn_body, is_async_fn, LimitStack}; use core::ops::ControlFlow; use rustc_ast::ast::Attribute; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId}; +use rustc_hir::{Body, Expr, ExprKind, FnDecl}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Span; use rustc_span::{sym, BytePos}; @@ -140,9 +141,8 @@ impl<'tcx> LateLintPass<'tcx> for CognitiveComplexity { decl: &'tcx FnDecl<'_>, body: &'tcx Body<'_>, span: Span, - hir_id: HirId, + def_id: LocalDefId, ) { - let def_id = cx.tcx.hir().local_def_id(hir_id); if !cx.tcx.has_attr(def_id.to_def_id(), sym::test) { let expr = if is_async_fn(kind) { match get_async_fn_body(cx.tcx, body) { diff --git a/src/tools/clippy/clippy_lints/src/copy_iterator.rs b/src/tools/clippy/clippy_lints/src/copy_iterator.rs index e38f77268..0fc115232 100644 --- a/src/tools/clippy/clippy_lints/src/copy_iterator.rs +++ b/src/tools/clippy/clippy_lints/src/copy_iterator.rs @@ -43,7 +43,7 @@ impl<'tcx> LateLintPass<'tcx> for CopyIterator { of_trait: Some(ref trait_ref), .. }) = item.kind; - let ty = cx.tcx.type_of(item.owner_id); + let ty = cx.tcx.type_of(item.owner_id).subst_identity(); if is_copy(cx, ty); if let Some(trait_id) = trait_ref.trait_def_id(); if cx.tcx.is_diagnostic_item(sym::Iterator, trait_id); diff --git a/src/tools/clippy/clippy_lints/src/declared_lints.rs b/src/tools/clippy/clippy_lints/src/declared_lints.rs index 91ca73633..cd5dd7a57 100644 --- a/src/tools/clippy/clippy_lints/src/declared_lints.rs +++ b/src/tools/clippy/clippy_lints/src/declared_lints.rs @@ -156,6 +156,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::exhaustive_items::EXHAUSTIVE_STRUCTS_INFO, crate::exit::EXIT_INFO, crate::explicit_write::EXPLICIT_WRITE_INFO, + crate::extra_unused_type_parameters::EXTRA_UNUSED_TYPE_PARAMETERS_INFO, crate::fallible_impl_from::FALLIBLE_IMPL_FROM_INFO, crate::float_literal::EXCESSIVE_PRECISION_INFO, crate::float_literal::LOSSY_FLOAT_LITERAL_INFO, @@ -178,6 +179,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::from_raw_with_void_ptr::FROM_RAW_WITH_VOID_PTR_INFO, crate::from_str_radix_10::FROM_STR_RADIX_10_INFO, crate::functions::DOUBLE_MUST_USE_INFO, + crate::functions::IMPL_TRAIT_IN_PARAMS_INFO, crate::functions::MISNAMED_GETTERS_INFO, crate::functions::MUST_USE_CANDIDATE_INFO, crate::functions::MUST_USE_UNIT_INFO, @@ -223,6 +225,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::let_underscore::LET_UNDERSCORE_FUTURE_INFO, crate::let_underscore::LET_UNDERSCORE_LOCK_INFO, crate::let_underscore::LET_UNDERSCORE_MUST_USE_INFO, + crate::let_underscore::LET_UNDERSCORE_UNTYPED_INFO, crate::lifetimes::EXTRA_UNUSED_LIFETIMES_INFO, crate::lifetimes::NEEDLESS_LIFETIMES_INFO, crate::literal_representation::DECIMAL_LITERAL_REPRESENTATION_INFO, @@ -377,6 +380,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::SKIP_WHILE_NEXT_INFO, crate::methods::STABLE_SORT_PRIMITIVE_INFO, crate::methods::STRING_EXTEND_CHARS_INFO, + crate::methods::SUSPICIOUS_COMMAND_ARG_SPACE_INFO, crate::methods::SUSPICIOUS_MAP_INFO, crate::methods::SUSPICIOUS_SPLITN_INFO, crate::methods::SUSPICIOUS_TO_OWNED_INFO, @@ -422,6 +426,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::module_style::MOD_MODULE_FILES_INFO, crate::module_style::SELF_NAMED_MODULE_FILES_INFO, crate::multi_assignments::MULTI_ASSIGNMENTS_INFO, + crate::multiple_unsafe_ops_per_block::MULTIPLE_UNSAFE_OPS_PER_BLOCK_INFO, crate::mut_key::MUTABLE_KEY_TYPE_INFO, crate::mut_mut::MUT_MUT_INFO, crate::mut_reference::UNNECESSARY_MUT_PASSED_INFO, @@ -445,6 +450,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::no_effect::NO_EFFECT_INFO, crate::no_effect::NO_EFFECT_UNDERSCORE_BINDING_INFO, crate::no_effect::UNNECESSARY_OPERATION_INFO, + crate::no_mangle_with_rust_abi::NO_MANGLE_WITH_RUST_ABI_INFO, crate::non_copy_const::BORROW_INTERIOR_MUTABLE_CONST_INFO, crate::non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST_INFO, crate::non_expressive_names::JUST_UNDERSCORES_AND_DIGITS_INFO, @@ -504,6 +510,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::ptr_offset_with_cast::PTR_OFFSET_WITH_CAST_INFO, crate::pub_use::PUB_USE_INFO, crate::question_mark::QUESTION_MARK_INFO, + crate::question_mark_used::QUESTION_MARK_USED_INFO, crate::ranges::MANUAL_RANGE_CONTAINS_INFO, crate::ranges::RANGE_MINUS_ONE_INFO, crate::ranges::RANGE_PLUS_ONE_INFO, @@ -534,6 +541,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::shadow::SHADOW_REUSE_INFO, crate::shadow::SHADOW_SAME_INFO, crate::shadow::SHADOW_UNRELATED_INFO, + crate::significant_drop_tightening::SIGNIFICANT_DROP_TIGHTENING_INFO, crate::single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES_INFO, crate::single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS_INFO, crate::size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT_INFO, @@ -571,6 +579,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::transmute::TRANSMUTE_INT_TO_BOOL_INFO, crate::transmute::TRANSMUTE_INT_TO_CHAR_INFO, crate::transmute::TRANSMUTE_INT_TO_FLOAT_INFO, + crate::transmute::TRANSMUTE_INT_TO_NON_ZERO_INFO, crate::transmute::TRANSMUTE_NULL_TO_FN_INFO, crate::transmute::TRANSMUTE_NUM_TO_BYTES_INFO, crate::transmute::TRANSMUTE_PTR_TO_PTR_INFO, diff --git a/src/tools/clippy/clippy_lints/src/default.rs b/src/tools/clippy/clippy_lints/src/default.rs index a04693f46..080d44e63 100644 --- a/src/tools/clippy/clippy_lints/src/default.rs +++ b/src/tools/clippy/clippy_lints/src/default.rs @@ -150,7 +150,7 @@ impl<'tcx> LateLintPass<'tcx> for Default { .fields .iter() .all(|field| { - is_copy(cx, cx.tcx.type_of(field.did)) + is_copy(cx, cx.tcx.type_of(field.did).subst_identity()) }); if !has_drop(cx, binding_type) || all_fields_are_copy; then { diff --git a/src/tools/clippy/clippy_lints/src/default_numeric_fallback.rs b/src/tools/clippy/clippy_lints/src/default_numeric_fallback.rs index 03460689e..4e1a6cd4d 100644 --- a/src/tools/clippy/clippy_lints/src/default_numeric_fallback.rs +++ b/src/tools/clippy/clippy_lints/src/default_numeric_fallback.rs @@ -141,7 +141,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> { ExprKind::MethodCall(_, receiver, args, _) => { if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(expr.hir_id) { - let fn_sig = self.cx.tcx.fn_sig(def_id).skip_binder(); + let fn_sig = self.cx.tcx.fn_sig(def_id).subst_identity().skip_binder(); for (expr, bound) in iter::zip(std::iter::once(*receiver).chain(args.iter()), fn_sig.inputs()) { self.ty_bounds.push((*bound).into()); self.visit_expr(expr); @@ -167,7 +167,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> { .iter() .find_map(|f_def| { if f_def.ident(self.cx.tcx) == field.ident - { Some(self.cx.tcx.type_of(f_def.did)) } + { Some(self.cx.tcx.type_of(f_def.did).subst_identity()) } else { None } }); self.ty_bounds.push(bound.into()); @@ -215,7 +215,7 @@ fn fn_sig_opt<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<PolyFnSig<' let node_ty = cx.typeck_results().node_type_opt(hir_id)?; // We can't use `Ty::fn_sig` because it automatically performs substs, this may result in FNs. match node_ty.kind() { - ty::FnDef(def_id, _) => Some(cx.tcx.fn_sig(*def_id)), + ty::FnDef(def_id, _) => Some(cx.tcx.fn_sig(*def_id).subst_identity()), ty::FnPtr(fn_sig) => Some(*fn_sig), _ => None, } diff --git a/src/tools/clippy/clippy_lints/src/dereference.rs b/src/tools/clippy/clippy_lints/src/dereference.rs index 05f2b92c0..7f3f26bed 100644 --- a/src/tools/clippy/clippy_lints/src/dereference.rs +++ b/src/tools/clippy/clippy_lints/src/dereference.rs @@ -3,7 +3,7 @@ use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exact use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; -use clippy_utils::ty::{expr_sig, is_copy, peel_mid_ty_refs, ty_sig, variant_of_res}; +use clippy_utils::ty::{adt_and_variant_of_res, expr_sig, is_copy, peel_mid_ty_refs, ty_sig}; use clippy_utils::{ fn_def_id, get_parent_expr, get_parent_expr_for_hir, is_lint_allowed, path_to_local, walk_to_expr_usage, }; @@ -26,8 +26,8 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::{Rvalue, StatementKind}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::{ - self, Binder, BoundVariableKind, Clause, EarlyBinder, FnSig, GenericArgKind, List, ParamTy, PredicateKind, - ProjectionPredicate, Ty, TyCtxt, TypeVisitable, TypeckResults, + self, Binder, BoundVariableKind, Clause, EarlyBinder, FnSig, GenericArgKind, List, ParamEnv, ParamTy, + PredicateKind, ProjectionPredicate, Ty, TyCtxt, TypeVisitableExt, TypeckResults, }; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{symbol::sym, Span, Symbol}; @@ -735,8 +735,8 @@ fn walk_parents<'tcx>( span, .. }) if span.ctxt() == ctxt => { - let ty = cx.tcx.type_of(owner_id.def_id); - Some(ty_auto_deref_stability(cx, ty, precedence).position_for_result(cx)) + let ty = cx.tcx.type_of(owner_id.def_id).subst_identity(); + Some(ty_auto_deref_stability(cx.tcx, cx.param_env, ty, precedence).position_for_result(cx)) }, Node::Item(&Item { @@ -759,8 +759,8 @@ fn walk_parents<'tcx>( }) if span.ctxt() == ctxt => { let output = cx .tcx - .erase_late_bound_regions(cx.tcx.fn_sig(owner_id.to_def_id()).output()); - Some(ty_auto_deref_stability(cx, output, precedence).position_for_result(cx)) + .erase_late_bound_regions(cx.tcx.fn_sig(owner_id).subst_identity().output()); + Some(ty_auto_deref_stability(cx.tcx, cx.param_env, output, precedence).position_for_result(cx)) }, Node::ExprField(field) if field.span.ctxt() == ctxt => match get_parent_expr_for_hir(cx, field.hir_id) { @@ -768,31 +768,44 @@ fn walk_parents<'tcx>( hir_id, kind: ExprKind::Struct(path, ..), .. - }) => variant_of_res(cx, cx.qpath_res(path, *hir_id)) - .and_then(|variant| variant.fields.iter().find(|f| f.name == field.ident.name)) - .map(|field_def| { - ty_auto_deref_stability(cx, cx.tcx.type_of(field_def.did), precedence).position_for_arg() + }) => adt_and_variant_of_res(cx, cx.qpath_res(path, *hir_id)) + .and_then(|(adt, variant)| { + variant + .fields + .iter() + .find(|f| f.name == field.ident.name) + .map(|f| (adt, f)) + }) + .map(|(adt, field_def)| { + ty_auto_deref_stability( + cx.tcx, + // Use the param_env of the target type. + cx.tcx.param_env(adt.did()), + cx.tcx.type_of(field_def.did).subst_identity(), + precedence, + ) + .position_for_arg() }), _ => None, }, Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind { ExprKind::Ret(_) => { - let owner_id = cx.tcx.hir().body_owner(cx.enclosing_body.unwrap()); + let owner_id = cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()); Some( if let Node::Expr( closure_expr @ Expr { kind: ExprKind::Closure(closure), .. }, - ) = cx.tcx.hir().get(owner_id) + ) = cx.tcx.hir().get_by_def_id(owner_id) { closure_result_position(cx, closure, cx.typeck_results().expr_ty(closure_expr), precedence) } else { let output = cx .tcx - .erase_late_bound_regions(cx.tcx.fn_sig(cx.tcx.hir().local_def_id(owner_id)).output()); - ty_auto_deref_stability(cx, output, precedence).position_for_result(cx) + .erase_late_bound_regions(cx.tcx.fn_sig(owner_id).subst_identity().output()); + ty_auto_deref_stability(cx.tcx, cx.param_env, output, precedence).position_for_result(cx) }, ) }, @@ -835,15 +848,20 @@ fn walk_parents<'tcx>( msrv, ) } else { - ty_auto_deref_stability(cx, cx.tcx.erase_late_bound_regions(ty), precedence) - .position_for_arg() + ty_auto_deref_stability( + cx.tcx, + // Use the param_env of the target function. + sig.predicates_id().map_or(ParamEnv::empty(), |id| cx.tcx.param_env(id)), + cx.tcx.erase_late_bound_regions(ty), + precedence + ).position_for_arg() } }, } }) }), ExprKind::MethodCall(method, receiver, args, _) => { - let id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(); + let fn_id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(); if receiver.hir_id == child_id { // Check for calls to trait methods where the trait is implemented on a reference. // Two cases need to be handled: @@ -852,13 +870,17 @@ fn walk_parents<'tcx>( // priority. if e.hir_id != child_id { return Some(Position::ReborrowStable(precedence)) - } else if let Some(trait_id) = cx.tcx.trait_of_item(id) + } else if let Some(trait_id) = cx.tcx.trait_of_item(fn_id) && let arg_ty = cx.tcx.erase_regions(cx.typeck_results().expr_ty_adjusted(e)) && let ty::Ref(_, sub_ty, _) = *arg_ty.kind() && let subs = cx .typeck_results() .node_substs_opt(parent.hir_id).map(|subs| &subs[1..]).unwrap_or_default() - && let impl_ty = if cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref() { + && let impl_ty = if cx.tcx.fn_sig(fn_id) + .subst_identity() + .skip_binder() + .inputs()[0].is_ref() + { // Trait methods taking `&self` sub_ty } else { @@ -879,10 +901,13 @@ fn walk_parents<'tcx>( return Some(Position::MethodReceiver); } args.iter().position(|arg| arg.hir_id == child_id).map(|i| { - let ty = cx.tcx.fn_sig(id).skip_binder().inputs()[i + 1]; + let ty = cx.tcx.fn_sig(fn_id).subst_identity().input(i + 1); // `e.hir_id == child_id` for https://github.com/rust-lang/rust-clippy/issues/9739 // `method.args.is_none()` for https://github.com/rust-lang/rust-clippy/issues/9782 - if e.hir_id == child_id && method.args.is_none() && let ty::Param(param_ty) = ty.kind() { + if e.hir_id == child_id + && method.args.is_none() + && let ty::Param(param_ty) = ty.skip_binder().kind() + { needless_borrow_impl_arg_position( cx, possible_borrowers, @@ -895,8 +920,10 @@ fn walk_parents<'tcx>( ) } else { ty_auto_deref_stability( - cx, - cx.tcx.erase_late_bound_regions(cx.tcx.fn_sig(id).input(i + 1)), + cx.tcx, + // Use the param_env of the target function. + cx.tcx.param_env(fn_id), + cx.tcx.erase_late_bound_regions(ty), precedence, ) .position_for_arg() @@ -1022,7 +1049,7 @@ fn binding_ty_auto_deref_stability<'tcx>( )) .is_sized(cx.tcx, cx.param_env.without_caller_bounds()), ), - TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(..) | TyKind::TraitObject(..) | TyKind::Err => { + TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(..) | TyKind::TraitObject(..) | TyKind::Err(_) => { Position::ReborrowStable(precedence) }, }; @@ -1038,7 +1065,7 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool { if self.0 || matches!( ty.kind, - TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(_) | TyKind::Err + TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(_) | TyKind::Err(_) ) { self.0 = true; @@ -1093,7 +1120,7 @@ fn needless_borrow_impl_arg_position<'tcx>( let sized_trait_def_id = cx.tcx.lang_items().sized_trait(); let Some(callee_def_id) = fn_def_id(cx, parent) else { return Position::Other(precedence) }; - let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder(); + let fn_sig = cx.tcx.fn_sig(callee_def_id).subst_identity().skip_binder(); let substs_with_expr_ty = cx .typeck_results() .node_substs(if let ExprKind::Call(callee, _) = parent.kind { @@ -1221,7 +1248,7 @@ fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool { .in_definition_order() .any(|assoc_item| { if assoc_item.fn_has_self_parameter { - let self_ty = cx.tcx.fn_sig(assoc_item.def_id).skip_binder().inputs()[0]; + let self_ty = cx.tcx.fn_sig(assoc_item.def_id).subst_identity().skip_binder().inputs()[0]; matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Mut)) } else { false @@ -1330,10 +1357,10 @@ fn replace_types<'tcx>( && let Some(term_ty) = projection_predicate.term.ty() && let ty::Param(term_param_ty) = term_ty.kind() { - let item_def_id = projection_predicate.projection_ty.def_id; - let assoc_item = cx.tcx.associated_item(item_def_id); - let projection = cx.tcx - .mk_projection(assoc_item.def_id, cx.tcx.mk_substs_trait(new_ty, [])); + let projection = cx.tcx.mk_ty_from_kind(ty::Alias( + ty::Projection, + projection_predicate.projection_ty.with_self_ty(cx.tcx, new_ty), + )); if let Ok(projected_ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, projection) && substs[term_param_ty.index as usize] != ty::GenericArg::from(projected_ty) @@ -1378,11 +1405,18 @@ impl<'tcx> TyPosition<'tcx> { } // Checks whether a type is stable when switching to auto dereferencing, -fn ty_auto_deref_stability<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, precedence: i8) -> TyPosition<'tcx> { +fn ty_auto_deref_stability<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ParamEnv<'tcx>, + ty: Ty<'tcx>, + precedence: i8, +) -> TyPosition<'tcx> { let ty::Ref(_, mut ty, _) = *ty.kind() else { return Position::Other(precedence).into(); }; + ty = tcx.try_normalize_erasing_regions(param_env, ty).unwrap_or(ty); + loop { break match *ty.kind() { ty::Ref(_, ref_ty, _) => { @@ -1419,12 +1453,11 @@ fn ty_auto_deref_stability<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, precedenc | ty::FnDef(..) | ty::Generator(..) | ty::GeneratorWitness(..) + | ty::GeneratorWitnessMIR(..) | ty::Closure(..) | ty::Never | ty::Tuple(_) - | ty::Alias(ty::Projection, _) => { - Position::DerefStable(precedence, ty.is_sized(cx.tcx, cx.param_env.without_caller_bounds())).into() - }, + | ty::Alias(ty::Projection, _) => Position::DerefStable(precedence, ty.is_sized(tcx, param_env)).into(), }; } } diff --git a/src/tools/clippy/clippy_lints/src/derivable_impls.rs b/src/tools/clippy/clippy_lints/src/derivable_impls.rs index bc18e2e5e..f95b8ccf0 100644 --- a/src/tools/clippy/clippy_lints/src/derivable_impls.rs +++ b/src/tools/clippy/clippy_lints/src/derivable_impls.rs @@ -184,7 +184,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls { if let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir); if let ImplItemKind::Fn(_, b) = &impl_item.kind; if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b); - if let Some(adt_def) = cx.tcx.type_of(item.owner_id).ty_adt_def(); + if let Some(adt_def) = cx.tcx.type_of(item.owner_id).subst_identity().ty_adt_def(); if let attrs = cx.tcx.hir().attrs(item.hir_id()); if !attrs.iter().any(|attr| attr.doc_str().is_some()); if let child_attrs = cx.tcx.hir().attrs(impl_item_hir); diff --git a/src/tools/clippy/clippy_lints/src/derive.rs b/src/tools/clippy/clippy_lints/src/derive.rs index 248d73884..b8428d66a 100644 --- a/src/tools/clippy/clippy_lints/src/derive.rs +++ b/src/tools/clippy/clippy_lints/src/derive.rs @@ -7,7 +7,7 @@ use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor}; use rustc_hir::{ - self as hir, BlockCheckMode, BodyId, Constness, Expr, ExprKind, FnDecl, HirId, Impl, Item, ItemKind, UnsafeSource, + self as hir, BlockCheckMode, BodyId, Constness, Expr, ExprKind, FnDecl, Impl, Item, ItemKind, UnsafeSource, Unsafety, }; use rustc_lint::{LateContext, LateLintPass}; @@ -18,6 +18,7 @@ use rustc_middle::ty::{ TraitPredicate, Ty, TyCtxt, }; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Span; use rustc_span::sym; @@ -210,7 +211,7 @@ impl<'tcx> LateLintPass<'tcx> for Derive { .. }) = item.kind { - let ty = cx.tcx.type_of(item.owner_id); + let ty = cx.tcx.type_of(item.owner_id).subst_identity(); let is_automatically_derived = cx.tcx.has_attr(item.owner_id.to_def_id(), sym::automatically_derived); check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived); @@ -346,7 +347,7 @@ fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &h let has_copy_impl = cx.tcx.all_local_trait_impls(()).get(©_id).map_or(false, |impls| { impls .iter() - .any(|&id| matches!(cx.tcx.type_of(id).kind(), ty::Adt(adt, _) if ty_adt.did() == adt.did())) + .any(|&id| matches!(cx.tcx.type_of(id).subst_identity().kind(), ty::Adt(adt, _) if ty_adt.did() == adt.did())) }); if !has_copy_impl { return; @@ -425,7 +426,7 @@ struct UnsafeVisitor<'a, 'tcx> { impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { type NestedFilter = nested_filter::All; - fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, _: Span, id: HirId) { + fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, _: Span, id: LocalDefId) { if self.has_unsafe { return; } @@ -513,7 +514,7 @@ fn param_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> } ParamEnv::new( - tcx.mk_predicates(ty_predicates.iter().map(|&(p, _)| p).chain( + tcx.mk_predicates_from_iter(ty_predicates.iter().map(|&(p, _)| p).chain( params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| { tcx.mk_predicate(Binder::dummy(PredicateKind::Clause(Clause::Trait(TraitPredicate { trait_ref: tcx.mk_trait_ref(eq_trait_id, [tcx.mk_param_from_def(param)]), diff --git a/src/tools/clippy/clippy_lints/src/doc.rs b/src/tools/clippy/clippy_lints/src/doc.rs index cdc23a4d2..384aca7fe 100644 --- a/src/tools/clippy/clippy_lints/src/doc.rs +++ b/src/tools/clippy/clippy_lints/src/doc.rs @@ -6,12 +6,17 @@ use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::{is_entrypoint_fn, method_chain_args, return_ty}; use if_chain::if_chain; use itertools::Itertools; +use pulldown_cmark::Event::{ + Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text, +}; +use pulldown_cmark::Tag::{CodeBlock, Heading, Item, Link, Paragraph}; +use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options}; use rustc_ast::ast::{Async, AttrKind, Attribute, Fn, FnRetTy, ItemKind}; use rustc_ast::token::CommentKind; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::sync::Lrc; use rustc_errors::emitter::EmitterWriter; -use rustc_errors::{Applicability, Handler, SuggestionStyle}; +use rustc_errors::{Applicability, Handler, SuggestionStyle, TerminalUrl}; use rustc_hir as hir; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{AnonConst, Expr}; @@ -23,7 +28,6 @@ use rustc_parse::maybe_new_parser_from_source_str; use rustc_parse::parser::ForceCollect; use rustc_session::parse::ParseSess; use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::def_id::LocalDefId; use rustc_span::edition::Edition; use rustc_span::source_map::{BytePos, FilePathMapping, SourceMap, Span}; use rustc_span::{sym, FileName, Pos}; @@ -251,7 +255,7 @@ declare_clippy_lint! { /// unimplemented!(); /// } /// ``` - #[clippy::version = "1.66.0"] + #[clippy::version = "1.67.0"] pub UNNECESSARY_SAFETY_DOC, restriction, "`pub fn` or `pub trait` with `# Safety` docs" @@ -302,7 +306,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown { panic_span: None, }; fpu.visit_expr(body.value); - lint_for_missing_headers(cx, item.owner_id.def_id, sig, headers, Some(body_id), fpu.panic_span); + lint_for_missing_headers(cx, item.owner_id, sig, headers, Some(body_id), fpu.panic_span); } }, hir::ItemKind::Impl(impl_) => { @@ -338,7 +342,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown { let Some(headers) = check_attrs(cx, &self.valid_idents, attrs) else { return }; if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind { if !in_external_macro(cx.tcx.sess, item.span) { - lint_for_missing_headers(cx, item.owner_id.def_id, sig, headers, None, None); + lint_for_missing_headers(cx, item.owner_id, sig, headers, None, None); } } } @@ -357,20 +361,20 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown { panic_span: None, }; fpu.visit_expr(body.value); - lint_for_missing_headers(cx, item.owner_id.def_id, sig, headers, Some(body_id), fpu.panic_span); + lint_for_missing_headers(cx, item.owner_id, sig, headers, Some(body_id), fpu.panic_span); } } } fn lint_for_missing_headers( cx: &LateContext<'_>, - def_id: LocalDefId, + owner_id: hir::OwnerId, sig: &hir::FnSig<'_>, headers: DocHeaders, body_id: Option<hir::BodyId>, panic_span: Option<Span>, ) { - if !cx.effective_visibilities.is_exported(def_id) { + if !cx.effective_visibilities.is_exported(owner_id.def_id) { return; // Private functions do not require doc comments } @@ -378,13 +382,13 @@ fn lint_for_missing_headers( if cx .tcx .hir() - .parent_iter(cx.tcx.hir().local_def_id_to_hir_id(def_id)) + .parent_iter(owner_id.into()) .any(|(id, _node)| is_doc_hidden(cx.tcx.hir().attrs(id))) { return; } - let span = cx.tcx.def_span(def_id); + let span = cx.tcx.def_span(owner_id); match (headers.safety, sig.header.unsafety) { (false, hir::Unsafety::Unsafe) => span_lint( cx, @@ -411,8 +415,7 @@ fn lint_for_missing_headers( ); } if !headers.errors { - let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id); - if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::Result) { + if is_type_diagnostic_item(cx, return_ty(cx, owner_id), sym::Result) { span_lint( cx, MISSING_ERRORS_DOC, @@ -499,7 +502,6 @@ struct DocHeaders { } fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[Attribute]) -> Option<DocHeaders> { - use pulldown_cmark::{BrokenLink, CowStr, Options}; /// We don't want the parser to choke on intra doc links. Since we don't /// actually care about rendering them, just pretend that all broken links are /// point to a fake address. @@ -540,8 +542,6 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[ pulldown_cmark::Parser::new_with_broken_link_callback(&doc, Options::empty(), Some(&mut cb)).into_offset_iter(); // Iterate over all `Events` and combine consecutive events into one let events = parser.coalesce(|previous, current| { - use pulldown_cmark::Event::Text; - let previous_range = previous.1; let current_range = current.1; @@ -566,12 +566,6 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize spans: &[(usize, Span)], ) -> DocHeaders { // true if a safety header was found - use pulldown_cmark::Event::{ - Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text, - }; - use pulldown_cmark::Tag::{CodeBlock, Heading, Item, Link, Paragraph}; - use pulldown_cmark::{CodeBlockKind, CowStr}; - let mut headers = DocHeaders::default(); let mut in_code = false; let mut in_link = None; @@ -662,6 +656,12 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize check_link_quotes(cx, in_link.is_some(), trimmed_text, span, &range, begin, text.len()); // Adjust for the beginning of the current `Event` let span = span.with_lo(span.lo() + BytePos::from_usize(range.start - begin)); + if let Some(link) = in_link.as_ref() + && let Ok(url) = Url::parse(link) + && (url.scheme() == "https" || url.scheme() == "http") { + // Don't check the text associated with external URLs + continue; + } text_to_check.push((text, span)); } }, @@ -707,7 +707,7 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) { let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); let fallback_bundle = - rustc_errors::fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false); + rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false); let emitter = EmitterWriter::new( Box::new(io::sink()), None, @@ -719,6 +719,7 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) { None, false, false, + TerminalUrl::No, ); let handler = Handler::with_emitter(false, None, Box::new(emitter)); let sess = ParseSess::with_span_handler(handler, sm); diff --git a/src/tools/clippy/clippy_lints/src/empty_enum.rs b/src/tools/clippy/clippy_lints/src/empty_enum.rs index 0570c2a10..d94664daa 100644 --- a/src/tools/clippy/clippy_lints/src/empty_enum.rs +++ b/src/tools/clippy/clippy_lints/src/empty_enum.rs @@ -49,7 +49,7 @@ impl<'tcx> LateLintPass<'tcx> for EmptyEnum { } if let ItemKind::Enum(..) = item.kind { - let ty = cx.tcx.type_of(item.owner_id); + let ty = cx.tcx.type_of(item.owner_id).subst_identity(); let adt = ty.ty_adt_def().expect("already checked whether this is an enum"); if adt.variants().is_empty() { span_lint_and_help( diff --git a/src/tools/clippy/clippy_lints/src/entry.rs b/src/tools/clippy/clippy_lints/src/entry.rs index b44e62435..48a54f602 100644 --- a/src/tools/clippy/clippy_lints/src/entry.rs +++ b/src/tools/clippy/clippy_lints/src/entry.rs @@ -6,7 +6,7 @@ use clippy_utils::{ source::{reindent_multiline, snippet_indent, snippet_with_applicability, snippet_with_context}, SpanlessEq, }; -use core::fmt::Write; +use core::fmt::{self, Write}; use rustc_errors::Applicability; use rustc_hir::{ hir_id::HirIdSet, @@ -65,6 +65,10 @@ declare_lint_pass!(HashMapPass => [MAP_ENTRY]); impl<'tcx> LateLintPass<'tcx> for HashMapPass { #[expect(clippy::too_many_lines)] fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if expr.span.from_expansion() { + return; + } + let Some(higher::If { cond: cond_expr, then: then_expr, r#else: else_expr }) = higher::If::hir(expr) else { return }; @@ -532,7 +536,7 @@ impl<'tcx> InsertSearchResults<'tcx> { if is_expr_used_or_unified(cx.tcx, insertion.call) { write_wrapped(&mut res, insertion, ctxt, app); } else { - let _ = write!( + let _: fmt::Result = write!( res, "e.insert({})", snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0 @@ -548,7 +552,7 @@ impl<'tcx> InsertSearchResults<'tcx> { ( self.snippet(cx, span, app, |res, insertion, ctxt, app| { // Insertion into a map would return `Some(&mut value)`, but the entry returns `&mut value` - let _ = write!( + let _: fmt::Result = write!( res, "Some(e.insert({}))", snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0 @@ -562,7 +566,7 @@ impl<'tcx> InsertSearchResults<'tcx> { ( self.snippet(cx, span, app, |res, insertion, ctxt, app| { // Insertion into a map would return `None`, but the entry returns a mutable reference. - let _ = if is_expr_final_block_expr(cx.tcx, insertion.call) { + let _: fmt::Result = if is_expr_final_block_expr(cx.tcx, insertion.call) { write!( res, "e.insert({});\n{}None", diff --git a/src/tools/clippy/clippy_lints/src/enum_clike.rs b/src/tools/clippy/clippy_lints/src/enum_clike.rs index da6788882..e275efaba 100644 --- a/src/tools/clippy/clippy_lints/src/enum_clike.rs +++ b/src/tools/clippy/clippy_lints/src/enum_clike.rs @@ -45,7 +45,7 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant { for var in def.variants { if let Some(anon_const) = &var.disr_expr { let def_id = cx.tcx.hir().body_owner_def_id(anon_const.body); - let mut ty = cx.tcx.type_of(def_id.to_def_id()); + let mut ty = cx.tcx.type_of(def_id.to_def_id()).subst_identity(); let constant = cx .tcx .const_eval_poly(def_id.to_def_id()) diff --git a/src/tools/clippy/clippy_lints/src/enum_variants.rs b/src/tools/clippy/clippy_lints/src/enum_variants.rs index b77b5621b..4c69dacf3 100644 --- a/src/tools/clippy/clippy_lints/src/enum_variants.rs +++ b/src/tools/clippy/clippy_lints/src/enum_variants.rs @@ -277,7 +277,7 @@ impl LateLintPass<'_> for EnumVariantNames { Some(c) if is_word_beginning(c) => span_lint( cx, MODULE_NAME_REPETITIONS, - item.span, + item.ident.span, "item name starts with its containing module's name", ), _ => (), @@ -287,7 +287,7 @@ impl LateLintPass<'_> for EnumVariantNames { span_lint( cx, MODULE_NAME_REPETITIONS, - item.span, + item.ident.span, "item name ends with its containing module's name", ); } diff --git a/src/tools/clippy/clippy_lints/src/escape.rs b/src/tools/clippy/clippy_lints/src/escape.rs index dfb438933..d6ab4c25e 100644 --- a/src/tools/clippy/clippy_lints/src/escape.rs +++ b/src/tools/clippy/clippy_lints/src/escape.rs @@ -8,6 +8,7 @@ use rustc_middle::mir::FakeReadCause; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::{self, TraitRef, Ty}; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Span; use rustc_span::symbol::kw; use rustc_target::spec::abi::Abi; @@ -63,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal { _: &'tcx FnDecl<'_>, body: &'tcx Body<'_>, _: Span, - hir_id: HirId, + fn_def_id: LocalDefId, ) { if let Some(header) = fn_kind.header() { if header.abi != Abi::Rust { @@ -71,7 +72,11 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal { } } - let parent_id = cx.tcx.hir().get_parent_item(hir_id).def_id; + let parent_id = cx + .tcx + .hir() + .get_parent_item(cx.tcx.hir().local_def_id_to_hir_id(fn_def_id)) + .def_id; let parent_node = cx.tcx.hir().find_by_def_id(parent_id); let mut trait_self_ty = None; @@ -84,7 +89,7 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal { // find `self` ty for this trait if relevant if let ItemKind::Trait(_, _, _, _, items) = item.kind { for trait_item in items { - if trait_item.id.hir_id() == hir_id { + if trait_item.id.owner_id.def_id == fn_def_id { // be sure we have `self` parameter in this function if trait_item.kind == (AssocItemKind::Fn { has_self: true }) { trait_self_ty = Some( @@ -105,7 +110,6 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal { too_large_for_stack: self.too_large_for_stack, }; - let fn_def_id = cx.tcx.hir().local_def_id(hir_id); let infcx = cx.tcx.infer_ctxt().build(); ExprUseVisitor::new(&mut v, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body); diff --git a/src/tools/clippy/clippy_lints/src/eta_reduction.rs b/src/tools/clippy/clippy_lints/src/eta_reduction.rs index 3543910c3..b2071f4dc 100644 --- a/src/tools/clippy/clippy_lints/src/eta_reduction.rs +++ b/src/tools/clippy/clippy_lints/src/eta_reduction.rs @@ -11,7 +11,7 @@ use rustc_hir::{Closure, Expr, ExprKind, Param, PatKind, Unsafety}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; use rustc_middle::ty::binding::BindingMode; -use rustc_middle::ty::{self, EarlyBinder, SubstsRef, Ty, TypeVisitable}; +use rustc_middle::ty::{self, EarlyBinder, SubstsRef, Ty, TypeVisitableExt}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::sym; @@ -108,7 +108,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction { if check_inputs(cx, body.params, None, args); let callee_ty = cx.typeck_results().expr_ty_adjusted(callee); let call_ty = cx.typeck_results().type_dependent_def_id(body.value.hir_id) - .map_or(callee_ty, |id| cx.tcx.type_of(id)); + .map_or(callee_ty, |id| cx.tcx.type_of(id).subst_identity()); if check_sig(cx, closure_ty, call_ty); let substs = cx.typeck_results().node_substs(callee.hir_id); // This fixes some false positives that I don't entirely understand @@ -153,7 +153,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction { if check_inputs(cx, body.params, Some(receiver), args); let method_def_id = cx.typeck_results().type_dependent_def_id(body.value.hir_id).unwrap(); let substs = cx.typeck_results().node_substs(body.value.hir_id); - let call_ty = cx.tcx.bound_type_of(method_def_id).subst(cx.tcx, substs); + let call_ty = cx.tcx.type_of(method_def_id).subst(cx.tcx, substs); if check_sig(cx, closure_ty, call_ty); then { span_lint_and_then(cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS, expr.span, "redundant closure", |diag| { @@ -233,7 +233,7 @@ fn get_ufcs_type_name<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId, substs match assoc_item.container { ty::TraitContainer => cx.tcx.def_path_str(def_id), ty::ImplContainer => { - let ty = cx.tcx.type_of(def_id); + let ty = cx.tcx.type_of(def_id).skip_binder(); match ty.kind() { ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did()), ty::Array(..) diff --git a/src/tools/clippy/clippy_lints/src/excessive_bools.rs b/src/tools/clippy/clippy_lints/src/excessive_bools.rs index fc2912f69..aef2db385 100644 --- a/src/tools/clippy/clippy_lints/src/excessive_bools.rs +++ b/src/tools/clippy/clippy_lints/src/excessive_bools.rs @@ -1,9 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::{get_parent_as_impl, has_repr_attr, is_bool}; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, FnDecl, HirId, Item, ItemKind, TraitFn, TraitItem, TraitItemKind, Ty}; +use rustc_hir::{Body, FnDecl, Item, ItemKind, TraitFn, TraitItem, TraitItemKind, Ty}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::def_id::LocalDefId; use rustc_span::Span; use rustc_target::spec::abi::Abi; @@ -168,8 +169,9 @@ impl<'tcx> LateLintPass<'tcx> for ExcessiveBools { fn_decl: &'tcx FnDecl<'tcx>, _: &'tcx Body<'tcx>, span: Span, - hir_id: HirId, + def_id: LocalDefId, ) { + let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id); if let Some(fn_header) = fn_kind.header() && fn_header.abi == Abi::Rust && get_parent_as_impl(cx.tcx, hir_id) diff --git a/src/tools/clippy/clippy_lints/src/exhaustive_items.rs b/src/tools/clippy/clippy_lints/src/exhaustive_items.rs index 1fece5d1c..9fd13084d 100644 --- a/src/tools/clippy/clippy_lints/src/exhaustive_items.rs +++ b/src/tools/clippy/clippy_lints/src/exhaustive_items.rs @@ -79,8 +79,7 @@ impl LateLintPass<'_> for ExhaustiveItems { then { let (lint, msg) = if let ItemKind::Struct(ref v, ..) = item.kind { if v.fields().iter().any(|f| { - let def_id = cx.tcx.hir().local_def_id(f.hir_id); - !cx.tcx.visibility(def_id).is_public() + !cx.tcx.visibility(f.def_id).is_public() }) { // skip structs with private fields return; diff --git a/src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs b/src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs new file mode 100644 index 000000000..20565e1d2 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs @@ -0,0 +1,229 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::trait_ref_of_method; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::MultiSpan; +use rustc_hir::intravisit::{walk_impl_item, walk_item, walk_param_bound, walk_ty, Visitor}; +use rustc_hir::{ + BodyId, ExprKind, GenericBound, GenericParamKind, Generics, ImplItem, ImplItemKind, Item, ItemKind, + PredicateOrigin, Ty, TyKind, WherePredicate, +}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::nested_filter; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{ + def_id::{DefId, LocalDefId}, + Span, +}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for type parameters in generics that are never used anywhere else. + /// + /// ### Why is this bad? + /// Functions cannot infer the value of unused type parameters; therefore, calling them + /// requires using a turbofish, which serves no purpose but to satisfy the compiler. + /// + /// ### Example + /// ```rust + /// fn unused_ty<T>(x: u8) { + /// // .. + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn no_unused_ty(x: u8) { + /// // .. + /// } + /// ``` + #[clippy::version = "1.69.0"] + pub EXTRA_UNUSED_TYPE_PARAMETERS, + complexity, + "unused type parameters in function definitions" +} + +pub struct ExtraUnusedTypeParameters { + avoid_breaking_exported_api: bool, +} + +impl ExtraUnusedTypeParameters { + pub fn new(avoid_breaking_exported_api: bool) -> Self { + Self { + avoid_breaking_exported_api, + } + } + + /// Don't lint external macros or functions with empty bodies. Also, don't lint public items if + /// the `avoid_breaking_exported_api` config option is set. + fn check_false_positive(&self, cx: &LateContext<'_>, span: Span, def_id: LocalDefId, body_id: BodyId) -> bool { + let body = cx.tcx.hir().body(body_id).value; + let fn_empty = matches!(&body.kind, ExprKind::Block(blk, None) if blk.stmts.is_empty() && blk.expr.is_none()); + let is_exported = cx.effective_visibilities.is_exported(def_id); + in_external_macro(cx.sess(), span) || (self.avoid_breaking_exported_api && is_exported) || fn_empty + } +} + +impl_lint_pass!(ExtraUnusedTypeParameters => [EXTRA_UNUSED_TYPE_PARAMETERS]); + +/// A visitor struct that walks a given function and gathers generic type parameters, plus any +/// trait bounds those parameters have. +struct TypeWalker<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + /// Collection of all the function's type parameters. + ty_params: FxHashMap<DefId, Span>, + /// Collection of any (inline) trait bounds corresponding to each type parameter. + bounds: FxHashMap<DefId, Span>, + /// The entire `Generics` object of the function, useful for querying purposes. + generics: &'tcx Generics<'tcx>, + /// The value of this will remain `true` if *every* parameter: + /// 1. Is a type parameter, and + /// 2. Goes unused in the function. + /// Otherwise, if any type parameters end up being used, or if any lifetime or const-generic + /// parameters are present, this will be set to `false`. + all_params_unused: bool, +} + +impl<'cx, 'tcx> TypeWalker<'cx, 'tcx> { + fn new(cx: &'cx LateContext<'tcx>, generics: &'tcx Generics<'tcx>) -> Self { + let mut all_params_unused = true; + let ty_params = generics + .params + .iter() + .filter_map(|param| { + if let GenericParamKind::Type { synthetic, .. } = param.kind { + (!synthetic).then_some((param.def_id.into(), param.span)) + } else { + if !param.is_elided_lifetime() { + all_params_unused = false; + } + None + } + }) + .collect(); + + Self { + cx, + ty_params, + bounds: FxHashMap::default(), + generics, + all_params_unused, + } + } + + fn mark_param_used(&mut self, def_id: DefId) { + if self.ty_params.remove(&def_id).is_some() { + self.all_params_unused = false; + } + } + + fn emit_lint(&self) { + let (msg, help) = match self.ty_params.len() { + 0 => return, + 1 => ( + "type parameter goes unused in function definition", + "consider removing the parameter", + ), + _ => ( + "type parameters go unused in function definition", + "consider removing the parameters", + ), + }; + + let source_map = self.cx.sess().source_map(); + let span = if self.all_params_unused { + self.generics.span.into() // Remove the entire list of generics + } else { + MultiSpan::from_spans( + self.ty_params + .iter() + .map(|(def_id, &span)| { + // Extend the span past any trait bounds, and include the comma at the end. + let span_to_extend = self.bounds.get(def_id).copied().map_or(span, Span::shrink_to_hi); + let comma_range = source_map.span_extend_to_next_char(span_to_extend, '>', false); + let comma_span = source_map.span_through_char(comma_range, ','); + span.with_hi(comma_span.hi()) + }) + .collect(), + ) + }; + + span_lint_and_help(self.cx, EXTRA_UNUSED_TYPE_PARAMETERS, span, msg, None, help); + } +} + +/// Given a generic bound, if the bound is for a trait that's not a `LangItem`, return the +/// `LocalDefId` for that trait. +fn bound_to_trait_def_id(bound: &GenericBound<'_>) -> Option<LocalDefId> { + bound.trait_ref()?.trait_def_id()?.as_local() +} + +impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> { + type NestedFilter = nested_filter::OnlyBodies; + + fn visit_ty(&mut self, t: &'tcx Ty<'tcx>) { + if let Some((def_id, _)) = t.peel_refs().as_generic_param() { + self.mark_param_used(def_id); + } else if let TyKind::OpaqueDef(id, _, _) = t.kind { + // Explicitly walk OpaqueDef. Normally `walk_ty` would do the job, but it calls + // `visit_nested_item`, which checks that `Self::NestedFilter::INTER` is set. We're + // using `OnlyBodies`, so the check ends up failing and the type isn't fully walked. + let item = self.nested_visit_map().item(id); + walk_item(self, item); + } else { + walk_ty(self, t); + } + } + + fn visit_where_predicate(&mut self, predicate: &'tcx WherePredicate<'tcx>) { + if let WherePredicate::BoundPredicate(predicate) = predicate { + // Collect spans for any bounds on type parameters. We only keep bounds that appear in + // the list of generics (not in a where-clause). + if let Some((def_id, _)) = predicate.bounded_ty.peel_refs().as_generic_param() { + // If the bound contains non-public traits, err on the safe side and don't lint the + // corresponding parameter. + if !predicate + .bounds + .iter() + .filter_map(bound_to_trait_def_id) + .all(|id| self.cx.effective_visibilities.is_exported(id)) + { + self.mark_param_used(def_id); + } else if let PredicateOrigin::GenericParam = predicate.origin { + self.bounds.insert(def_id, predicate.span); + } + } + // Only walk the right-hand side of where-bounds + for bound in predicate.bounds { + walk_param_bound(self, bound); + } + } + } + + fn nested_visit_map(&mut self) -> Self::Map { + self.cx.tcx.hir() + } +} + +impl<'tcx> LateLintPass<'tcx> for ExtraUnusedTypeParameters { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if let ItemKind::Fn(_, generics, body_id) = item.kind + && !self.check_false_positive(cx, item.span, item.owner_id.def_id, body_id) + { + let mut walker = TypeWalker::new(cx, generics); + walk_item(&mut walker, item); + walker.emit_lint(); + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'tcx>) { + // Only lint on inherent methods, not trait methods. + if let ImplItemKind::Fn(.., body_id) = item.kind + && trait_ref_of_method(cx, item.owner_id.def_id).is_none() + && !self.check_false_positive(cx, item.span, item.owner_id.def_id, body_id) + { + let mut walker = TypeWalker::new(cx, item.generics); + walk_impl_item(&mut walker, item); + walker.emit_lint(); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/format_args.rs b/src/tools/clippy/clippy_lints/src/format_args.rs index 04e6949e1..c511d85e9 100644 --- a/src/tools/clippy/clippy_lints/src/format_args.rs +++ b/src/tools/clippy/clippy_lints/src/format_args.rs @@ -7,14 +7,14 @@ use clippy_utils::macros::{ }; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_opt; -use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; +use clippy_utils::ty::{implements_trait, is_type_lang_item}; use if_chain::if_chain; use itertools::Itertools; use rustc_errors::{ Applicability, SuggestionStyle::{CompletelyHidden, ShowCode}, }; -use rustc_hir::{Expr, ExprKind, HirId, QPath}; +use rustc_hir::{Expr, ExprKind, HirId, LangItem, QPath}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::adjustment::{Adjust, Adjustment}; use rustc_middle::ty::Ty; @@ -237,7 +237,7 @@ fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) { ); } - if is_type_diagnostic_item(cx, param_ty, sym::Arguments) && !arg.format.is_default_for_trait() { + if is_type_lang_item(cx, param_ty, LangItem::FormatArguments) && !arg.format.is_default_for_trait() { span_lint_and_then( cx, UNUSED_FORMAT_SPECS, @@ -311,6 +311,10 @@ fn check_uninlined_args( // in those cases, make the code suggestion hidden let multiline_fix = fixes.iter().any(|(span, _)| cx.sess().source_map().is_multiline(*span)); + // Suggest removing each argument only once, for example in `format!("{0} {0}", arg)`. + fixes.sort_unstable_by_key(|(span, _)| *span); + fixes.dedup_by_key(|(span, _)| *span); + span_lint_and_then( cx, UNINLINED_FORMAT_ARGS, @@ -336,6 +340,7 @@ fn check_one_arg( if matches!(param.kind, Implicit | Starred | Named(_) | Numbered) && let ExprKind::Path(QPath::Resolved(None, path)) = param.value.kind && let [segment] = path.segments + && segment.args.is_none() && let Some(arg_span) = args.value_with_prev_comma_span(param.value.hir_id) { let replacement = match param.usage { diff --git a/src/tools/clippy/clippy_lints/src/from_raw_with_void_ptr.rs b/src/tools/clippy/clippy_lints/src/from_raw_with_void_ptr.rs index 00f5ba564..096508dc4 100644 --- a/src/tools/clippy/clippy_lints/src/from_raw_with_void_ptr.rs +++ b/src/tools/clippy/clippy_lints/src/from_raw_with_void_ptr.rs @@ -31,7 +31,7 @@ declare_clippy_lint! { /// let _ = unsafe { Box::from_raw(ptr as *mut usize) }; /// ``` /// - #[clippy::version = "1.66.0"] + #[clippy::version = "1.67.0"] pub FROM_RAW_WITH_VOID_PTR, suspicious, "creating a `Box` from a void raw pointer" diff --git a/src/tools/clippy/clippy_lints/src/functions/impl_trait_in_params.rs b/src/tools/clippy/clippy_lints/src/functions/impl_trait_in_params.rs new file mode 100644 index 000000000..2811a73f6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/functions/impl_trait_in_params.rs @@ -0,0 +1,50 @@ +use clippy_utils::{diagnostics::span_lint_and_then, is_in_test_function}; + +use rustc_hir::{intravisit::FnKind, Body, HirId}; +use rustc_lint::LateContext; +use rustc_span::Span; + +use super::IMPL_TRAIT_IN_PARAMS; + +pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body: &'tcx Body<'_>, hir_id: HirId) { + if cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public() && !is_in_test_function(cx.tcx, hir_id) + { + if let FnKind::ItemFn(ident, generics, _) = kind { + for param in generics.params { + if param.is_impl_trait() { + // No generics with nested generics, and no generics like FnMut(x) + span_lint_and_then( + cx, + IMPL_TRAIT_IN_PARAMS, + param.span, + "'`impl Trait` used as a function parameter'", + |diag| { + if let Some(gen_span) = generics.span_for_param_suggestion() { + diag.span_suggestion_with_style( + gen_span, + "add a type paremeter", + format!(", {{ /* Generic name */ }}: {}", ¶m.name.ident().as_str()[5..]), + rustc_errors::Applicability::HasPlaceholders, + rustc_errors::SuggestionStyle::ShowAlways, + ); + } else { + diag.span_suggestion_with_style( + Span::new( + body.params[0].span.lo() - rustc_span::BytePos(1), + ident.span.hi(), + ident.span.ctxt(), + ident.span.parent(), + ), + "add a type paremeter", + format!("<{{ /* Generic name */ }}: {}>", ¶m.name.ident().as_str()[5..]), + rustc_errors::Applicability::HasPlaceholders, + rustc_errors::SuggestionStyle::ShowAlways, + ); + } + }, + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/functions/misnamed_getters.rs b/src/tools/clippy/clippy_lints/src/functions/misnamed_getters.rs index 27acad45c..8b53ee68e 100644 --- a/src/tools/clippy/clippy_lints/src/functions/misnamed_getters.rs +++ b/src/tools/clippy/clippy_lints/src/functions/misnamed_getters.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use rustc_errors::Applicability; -use rustc_hir::{intravisit::FnKind, Body, ExprKind, FnDecl, HirId, ImplicitSelfKind, Unsafety}; +use rustc_hir::{intravisit::FnKind, Body, ExprKind, FnDecl, ImplicitSelfKind, Unsafety}; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::Span; @@ -10,14 +10,7 @@ use std::iter; use super::MISNAMED_GETTERS; -pub fn check_fn( - cx: &LateContext<'_>, - kind: FnKind<'_>, - decl: &FnDecl<'_>, - body: &Body<'_>, - span: Span, - _hir_id: HirId, -) { +pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body: &Body<'_>, span: Span) { let FnKind::Method(ref ident, sig) = kind else { return; }; diff --git a/src/tools/clippy/clippy_lints/src/functions/mod.rs b/src/tools/clippy/clippy_lints/src/functions/mod.rs index 9dbce3f88..d2852b4ac 100644 --- a/src/tools/clippy/clippy_lints/src/functions/mod.rs +++ b/src/tools/clippy/clippy_lints/src/functions/mod.rs @@ -1,3 +1,4 @@ +mod impl_trait_in_params; mod misnamed_getters; mod must_use; mod not_unsafe_ptr_arg_deref; @@ -9,6 +10,7 @@ use rustc_hir as hir; use rustc_hir::intravisit; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::def_id::LocalDefId; use rustc_span::Span; declare_clippy_lint! { @@ -326,6 +328,32 @@ declare_clippy_lint! { "getter method returning the wrong field" } +declare_clippy_lint! { + /// ### What it does + /// Lints when `impl Trait` is being used in a function's paremeters. + /// ### Why is this bad? + /// Turbofish syntax (`::<>`) cannot be used when `impl Trait` is being used, making `impl Trait` less powerful. Readability may also be a factor. + /// + /// ### Example + /// ```rust + /// trait MyTrait {} + /// fn foo(a: impl MyTrait) { + /// // [...] + /// } + /// ``` + /// Use instead: + /// ```rust + /// trait MyTrait {} + /// fn foo<T: MyTrait>(a: T) { + /// // [...] + /// } + /// ``` + #[clippy::version = "1.68.0"] + pub IMPL_TRAIT_IN_PARAMS, + restriction, + "`impl Trait` is used in the function's parameters" +} + #[derive(Copy, Clone)] pub struct Functions { too_many_arguments_threshold: u64, @@ -353,6 +381,7 @@ impl_lint_pass!(Functions => [ RESULT_UNIT_ERR, RESULT_LARGE_ERR, MISNAMED_GETTERS, + IMPL_TRAIT_IN_PARAMS, ]); impl<'tcx> LateLintPass<'tcx> for Functions { @@ -363,12 +392,14 @@ impl<'tcx> LateLintPass<'tcx> for Functions { decl: &'tcx hir::FnDecl<'_>, body: &'tcx hir::Body<'_>, span: Span, - hir_id: hir::HirId, + def_id: LocalDefId, ) { + let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id); too_many_arguments::check_fn(cx, kind, decl, span, hir_id, self.too_many_arguments_threshold); too_many_lines::check_fn(cx, kind, span, body, self.too_many_lines_threshold); - not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, hir_id); - misnamed_getters::check_fn(cx, kind, decl, body, span, hir_id); + not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, def_id); + misnamed_getters::check_fn(cx, kind, decl, body, span); + impl_trait_in_params::check_fn(cx, &kind, body, hir_id); } fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { diff --git a/src/tools/clippy/clippy_lints/src/functions/must_use.rs b/src/tools/clippy/clippy_lints/src/functions/must_use.rs index d22bede36..29bdc46b6 100644 --- a/src/tools/clippy/clippy_lints/src/functions/must_use.rs +++ b/src/tools/clippy/clippy_lints/src/functions/must_use.rs @@ -1,6 +1,6 @@ use rustc_ast::ast::Attribute; use rustc_errors::Applicability; -use rustc_hir::def_id::{DefIdSet, LocalDefId}; +use rustc_hir::def_id::DefIdSet; use rustc_hir::{self as hir, def::Res, QPath}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::{ @@ -27,14 +27,14 @@ pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_> let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id); let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); if let Some(attr) = attr { - check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr); + check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr); } else if is_public && !is_proc_macro(cx.sess(), attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) { check_must_use_candidate( cx, sig.decl, cx.tcx.hir().body(*body_id), item.span, - item.owner_id.def_id, + item.owner_id, item.span.with_hi(sig.decl.output.span().hi()), "this function could have a `#[must_use]` attribute", ); @@ -49,7 +49,7 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp let attrs = cx.tcx.hir().attrs(item.hir_id()); let attr = cx.tcx.get_attr(item.owner_id.to_def_id(), sym::must_use); if let Some(attr) = attr { - check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr); + check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr); } else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.owner_id.def_id).is_none() @@ -59,7 +59,7 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp sig.decl, cx.tcx.hir().body(*body_id), item.span, - item.owner_id.def_id, + item.owner_id, item.span.with_hi(sig.decl.output.span().hi()), "this method could have a `#[must_use]` attribute", ); @@ -75,7 +75,7 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr let attrs = cx.tcx.hir().attrs(item.hir_id()); let attr = cx.tcx.get_attr(item.owner_id.to_def_id(), sym::must_use); if let Some(attr) = attr { - check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr); + check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr); } else if let hir::TraitFn::Provided(eid) = *eid { let body = cx.tcx.hir().body(eid); if attr.is_none() && is_public && !is_proc_macro(cx.sess(), attrs) { @@ -84,7 +84,7 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr sig.decl, body, item.span, - item.owner_id.def_id, + item.owner_id, item.span.with_hi(sig.decl.output.span().hi()), "this method could have a `#[must_use]` attribute", ); @@ -96,7 +96,7 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr fn check_needless_must_use( cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, - item_id: hir::HirId, + item_id: hir::OwnerId, item_span: Span, fn_header_span: Span, attr: &Attribute, @@ -131,7 +131,7 @@ fn check_must_use_candidate<'tcx>( decl: &'tcx hir::FnDecl<'_>, body: &'tcx hir::Body<'_>, item_span: Span, - item_id: LocalDefId, + item_id: hir::OwnerId, fn_span: Span, msg: &str, ) { @@ -139,8 +139,8 @@ fn check_must_use_candidate<'tcx>( || mutates_static(cx, body) || in_external_macro(cx.sess(), item_span) || returns_unit(decl) - || !cx.effective_visibilities.is_exported(item_id) - || is_must_use_ty(cx, return_ty(cx, cx.tcx.hir().local_def_id_to_hir_id(item_id))) + || !cx.effective_visibilities.is_exported(item_id.def_id) + || is_must_use_ty(cx, return_ty(cx, item_id)) { return; } diff --git a/src/tools/clippy/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs b/src/tools/clippy/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs index 2c0bf551f..f2aa7b597 100644 --- a/src/tools/clippy/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs +++ b/src/tools/clippy/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs @@ -17,7 +17,7 @@ pub(super) fn check_fn<'tcx>( kind: intravisit::FnKind<'tcx>, decl: &'tcx hir::FnDecl<'tcx>, body: &'tcx hir::Body<'tcx>, - hir_id: hir::HirId, + def_id: LocalDefId, ) { let unsafety = match kind { intravisit::FnKind::ItemFn(_, _, hir::FnHeader { unsafety, .. }) => unsafety, @@ -25,7 +25,7 @@ pub(super) fn check_fn<'tcx>( intravisit::FnKind::Closure => return, }; - check_raw_ptr(cx, unsafety, decl, body, cx.tcx.hir().local_def_id(hir_id)); + check_raw_ptr(cx, unsafety, decl, body, def_id); } pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { @@ -58,7 +58,7 @@ fn check_raw_ptr<'tcx>( }, hir::ExprKind::MethodCall(_, recv, args, _) => { let def_id = typeck.type_dependent_def_id(e.hir_id).unwrap(); - if cx.tcx.fn_sig(def_id).skip_binder().unsafety == hir::Unsafety::Unsafe { + if cx.tcx.fn_sig(def_id).skip_binder().skip_binder().unsafety == hir::Unsafety::Unsafe { check_arg(cx, &raw_ptrs, recv); for arg in args { check_arg(cx, &raw_ptrs, arg); diff --git a/src/tools/clippy/clippy_lints/src/functions/result.rs b/src/tools/clippy/clippy_lints/src/functions/result.rs index 23da145d0..fa2a9b30c 100644 --- a/src/tools/clippy/clippy_lints/src/functions/result.rs +++ b/src/tools/clippy/clippy_lints/src/functions/result.rs @@ -21,7 +21,7 @@ fn result_err_ty<'tcx>( ) -> Option<(&'tcx hir::Ty<'tcx>, Ty<'tcx>)> { if !in_external_macro(cx.sess(), item_span) && let hir::FnRetTy::Return(hir_ty) = decl.output - && let ty = cx.tcx.erase_late_bound_regions(cx.tcx.fn_sig(id).output()) + && let ty = cx.tcx.erase_late_bound_regions(cx.tcx.fn_sig(id).subst_identity().output()) && is_type_diagnostic_item(cx, ty, sym::Result) && let ty::Adt(_, substs) = ty.kind() { diff --git a/src/tools/clippy/clippy_lints/src/future_not_send.rs b/src/tools/clippy/clippy_lints/src/future_not_send.rs index 989f83cf8..9fb73a371 100644 --- a/src/tools/clippy/clippy_lints/src/future_not_send.rs +++ b/src/tools/clippy/clippy_lints/src/future_not_send.rs @@ -1,11 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::return_ty; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, FnDecl, HirId}; +use rustc_hir::{Body, FnDecl}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, AliasTy, Clause, EarlyBinder, PredicateKind}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::def_id::LocalDefId; use rustc_span::{sym, Span}; use rustc_trait_selection::traits::error_reporting::suggestions::TypeErrCtxtExt; use rustc_trait_selection::traits::{self, FulfillmentError}; @@ -56,12 +57,12 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend { decl: &'tcx FnDecl<'tcx>, _: &'tcx Body<'tcx>, _: Span, - hir_id: HirId, + fn_def_id: LocalDefId, ) { if let FnKind::Closure = kind { return; } - let ret_ty = return_ty(cx, hir_id); + let ret_ty = return_ty(cx, cx.tcx.hir().local_def_id_to_hir_id(fn_def_id).expect_owner()); if let ty::Alias(ty::Opaque, AliasTy { def_id, substs, .. }) = *ret_ty.kind() { let preds = cx.tcx.explicit_item_bounds(def_id); let mut is_future = false; @@ -78,7 +79,7 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend { let send_trait = cx.tcx.get_diagnostic_item(sym::Send).unwrap(); let span = decl.output.span(); let infcx = cx.tcx.infer_ctxt().build(); - let cause = traits::ObligationCause::misc(span, hir_id); + let cause = traits::ObligationCause::misc(span, fn_def_id); let send_errors = traits::fully_solve_bound(&infcx, cause, cx.param_env, ret_ty, send_trait); if !send_errors.is_empty() { span_lint_and_then( diff --git a/src/tools/clippy/clippy_lints/src/implicit_return.rs b/src/tools/clippy/clippy_lints/src/implicit_return.rs index 946d04eff..372b6ead3 100644 --- a/src/tools/clippy/clippy_lints/src/implicit_return.rs +++ b/src/tools/clippy/clippy_lints/src/implicit_return.rs @@ -11,6 +11,7 @@ use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::def_id::LocalDefId; use rustc_span::{Span, SyntaxContext}; declare_clippy_lint! { @@ -223,7 +224,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitReturn { decl: &'tcx FnDecl<'_>, body: &'tcx Body<'_>, span: Span, - _: HirId, + _: LocalDefId, ) { if (!matches!(kind, FnKind::Closure) && matches!(decl.output, FnRetTy::DefaultReturn(_))) || span.ctxt() != body.value.span.ctxt() diff --git a/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs b/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs index 29d59c26d..0c7aea6da 100644 --- a/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs +++ b/src/tools/clippy/clippy_lints/src/implicit_saturating_sub.rs @@ -102,7 +102,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub { if let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id); if let Some(impl_id) = cx.tcx.impl_of_method(const_id); if let None = cx.tcx.impl_trait_ref(impl_id); // An inherent impl - if cx.tcx.type_of(impl_id).is_integral(); + if cx.tcx.type_of(impl_id).subst_identity().is_integral(); then { print_lint_and_sugg(cx, var_name, expr) } @@ -115,7 +115,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub { if let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id); if let Some(impl_id) = cx.tcx.impl_of_method(func_id); if let None = cx.tcx.impl_trait_ref(impl_id); // An inherent impl - if cx.tcx.type_of(impl_id).is_integral(); + if cx.tcx.type_of(impl_id).subst_identity().is_integral(); then { print_lint_and_sugg(cx, var_name, expr) } diff --git a/src/tools/clippy/clippy_lints/src/inconsistent_struct_constructor.rs b/src/tools/clippy/clippy_lints/src/inconsistent_struct_constructor.rs index e2f2d3d42..1ad886f2c 100644 --- a/src/tools/clippy/clippy_lints/src/inconsistent_struct_constructor.rs +++ b/src/tools/clippy/clippy_lints/src/inconsistent_struct_constructor.rs @@ -7,7 +7,7 @@ use rustc_hir::{self as hir, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::Symbol; -use std::fmt::Write as _; +use std::fmt::{self, Write as _}; declare_clippy_lint! { /// ### What it does @@ -90,7 +90,7 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor { let mut fields_snippet = String::new(); let (last_ident, idents) = ordered_fields.split_last().unwrap(); for ident in idents { - let _ = write!(fields_snippet, "{ident}, "); + let _: fmt::Result = write!(fields_snippet, "{ident}, "); } fields_snippet.push_str(&last_ident.to_string()); diff --git a/src/tools/clippy/clippy_lints/src/indexing_slicing.rs b/src/tools/clippy/clippy_lints/src/indexing_slicing.rs index eebfb753a..c384172fb 100644 --- a/src/tools/clippy/clippy_lints/src/indexing_slicing.rs +++ b/src/tools/clippy/clippy_lints/src/indexing_slicing.rs @@ -109,7 +109,7 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { if let Some(range) = higher::Range::hir(index) { // Ranged indexes, i.e., &x[n..m], &x[n..], &x[..n] and &x[..] if let ty::Array(_, s) = ty.kind() { - let size: u128 = if let Some(size) = s.try_eval_usize(cx.tcx, cx.param_env) { + let size: u128 = if let Some(size) = s.try_eval_target_usize(cx.tcx, cx.param_env) { size.into() } else { return; diff --git a/src/tools/clippy/clippy_lints/src/inherent_impl.rs b/src/tools/clippy/clippy_lints/src/inherent_impl.rs index e9b2e31a7..7c41699f3 100644 --- a/src/tools/clippy/clippy_lints/src/inherent_impl.rs +++ b/src/tools/clippy/clippy_lints/src/inherent_impl.rs @@ -66,7 +66,8 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { ) }) { for impl_id in impl_ids.iter().map(|id| id.expect_local()) { - match type_map.entry(cx.tcx.type_of(impl_id)) { + let impl_ty = cx.tcx.type_of(impl_id).subst_identity(); + match type_map.entry(impl_ty) { Entry::Vacant(e) => { // Store the id for the first impl block of this type. The span is retrieved lazily. e.insert(IdOrSpan::Id(impl_id)); diff --git a/src/tools/clippy/clippy_lints/src/inherent_to_string.rs b/src/tools/clippy/clippy_lints/src/inherent_to_string.rs index aaecc4fa8..d43e5cc9b 100644 --- a/src/tools/clippy/clippy_lints/src/inherent_to_string.rs +++ b/src/tools/clippy/clippy_lints/src/inherent_to_string.rs @@ -105,7 +105,7 @@ impl<'tcx> LateLintPass<'tcx> for InherentToString { if impl_item.generics.params.iter().all(|p| matches!(p.kind, GenericParamKind::Lifetime { .. })); // Check if return type is String - if is_type_lang_item(cx, return_ty(cx, impl_item.hir_id()), LangItem::String); + if is_type_lang_item(cx, return_ty(cx, impl_item.owner_id), LangItem::String); // Filters instances of to_string which are required by a trait if trait_ref_of_method(cx, impl_item.owner_id.def_id).is_none(); @@ -124,7 +124,7 @@ fn show_lint(cx: &LateContext<'_>, item: &ImplItem<'_>) { .expect("Failed to get trait ID of `Display`!"); // Get the real type of 'self' - let self_type = cx.tcx.fn_sig(item.owner_id).input(0); + let self_type = cx.tcx.fn_sig(item.owner_id).skip_binder().input(0); let self_type = self_type.skip_binder().peel_refs(); // Emit either a warning or an error diff --git a/src/tools/clippy/clippy_lints/src/instant_subtraction.rs b/src/tools/clippy/clippy_lints/src/instant_subtraction.rs index 9f6e89405..668110c7c 100644 --- a/src/tools/clippy/clippy_lints/src/instant_subtraction.rs +++ b/src/tools/clippy/clippy_lints/src/instant_subtraction.rs @@ -59,7 +59,7 @@ declare_clippy_lint! { /// /// [`Duration`]: std::time::Duration /// [`Instant::now()`]: std::time::Instant::now; - #[clippy::version = "1.65.0"] + #[clippy::version = "1.67.0"] pub UNCHECKED_DURATION_SUBTRACTION, pedantic, "finds unchecked subtraction of a 'Duration' from an 'Instant'" diff --git a/src/tools/clippy/clippy_lints/src/iter_not_returning_iterator.rs b/src/tools/clippy/clippy_lints/src/iter_not_returning_iterator.rs index e76de77f1..c924d7361 100644 --- a/src/tools/clippy/clippy_lints/src/iter_not_returning_iterator.rs +++ b/src/tools/clippy/clippy_lints/src/iter_not_returning_iterator.rs @@ -66,7 +66,9 @@ impl<'tcx> LateLintPass<'tcx> for IterNotReturningIterator { fn check_sig(cx: &LateContext<'_>, name: &str, sig: &FnSig<'_>, fn_id: LocalDefId) { if sig.decl.implicit_self.has_implicit_self() { - let ret_ty = cx.tcx.erase_late_bound_regions(cx.tcx.fn_sig(fn_id).output()); + let ret_ty = cx + .tcx + .erase_late_bound_regions(cx.tcx.fn_sig(fn_id).subst_identity().output()); let ret_ty = cx .tcx .try_normalize_erasing_regions(cx.param_env, ret_ty) diff --git a/src/tools/clippy/clippy_lints/src/large_const_arrays.rs b/src/tools/clippy/clippy_lints/src/large_const_arrays.rs index db637dfc0..4dc750c03 100644 --- a/src/tools/clippy/clippy_lints/src/large_const_arrays.rs +++ b/src/tools/clippy/clippy_lints/src/large_const_arrays.rs @@ -54,7 +54,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays { let ty = hir_ty_to_ty(cx.tcx, hir_ty); if let ty::Array(element_type, cst) = ty.kind(); if let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind(); - if let Ok(element_count) = element_count.try_to_machine_usize(cx.tcx); + if let Ok(element_count) = element_count.try_to_target_usize(cx.tcx); if let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()); if self.maximum_allowed_size < u128::from(element_count) * u128::from(element_size); diff --git a/src/tools/clippy/clippy_lints/src/large_enum_variant.rs b/src/tools/clippy/clippy_lints/src/large_enum_variant.rs index b8d4abdbb..1c99bd2f3 100644 --- a/src/tools/clippy/clippy_lints/src/large_enum_variant.rs +++ b/src/tools/clippy/clippy_lints/src/large_enum_variant.rs @@ -83,7 +83,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant { return; } if let ItemKind::Enum(ref def, _) = item.kind { - let ty = cx.tcx.type_of(item.owner_id); + let ty = cx.tcx.type_of(item.owner_id).subst_identity(); let Adt(adt, subst) = ty.kind() else { panic!("already checked whether this is an enum") }; diff --git a/src/tools/clippy/clippy_lints/src/large_stack_arrays.rs b/src/tools/clippy/clippy_lints/src/large_stack_arrays.rs index 89ae83d48..32c6312e0 100644 --- a/src/tools/clippy/clippy_lints/src/large_stack_arrays.rs +++ b/src/tools/clippy/clippy_lints/src/large_stack_arrays.rs @@ -41,7 +41,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackArrays { if let ExprKind::Repeat(_, _) = expr.kind && let ty::Array(element_type, cst) = cx.typeck_results().expr_ty(expr).kind() && let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind() - && let Ok(element_count) = element_count.try_to_machine_usize(cx.tcx) + && let Ok(element_count) = element_count.try_to_target_usize(cx.tcx) && let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()) && !cx.tcx.hir().parent_iter(expr.hir_id) .any(|(_, node)| matches!(node, Node::Item(Item { kind: ItemKind::Static(..), .. }))) diff --git a/src/tools/clippy/clippy_lints/src/len_zero.rs b/src/tools/clippy/clippy_lints/src/len_zero.rs index 3c70c9cf1..e13bc4797 100644 --- a/src/tools/clippy/clippy_lints/src/len_zero.rs +++ b/src/tools/clippy/clippy_lints/src/len_zero.rs @@ -135,6 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { if item.ident.name == sym::len; if let ImplItemKind::Fn(sig, _) = &item.kind; if sig.decl.implicit_self.has_implicit_self(); + if sig.decl.inputs.len() == 1; if cx.effective_visibilities.is_exported(item.owner_id.def_id); if matches!(sig.decl.output, FnRetTy::Return(_)); if let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id()); @@ -144,7 +145,7 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { if let Some(local_id) = ty_id.as_local(); let ty_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id); if !is_lint_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id); - if let Some(output) = parse_len_output(cx, cx.tcx.fn_sig(item.owner_id).skip_binder()); + if let Some(output) = parse_len_output(cx, cx.tcx.fn_sig(item.owner_id).subst_identity().skip_binder()); then { let (name, kind) = match cx.tcx.hir().find(ty_hir_id) { Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"), @@ -196,7 +197,15 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, trait_items fn is_named_self(cx: &LateContext<'_>, item: &TraitItemRef, name: Symbol) -> bool { item.ident.name == name && if let AssocItemKind::Fn { has_self } = item.kind { - has_self && { cx.tcx.fn_sig(item.id.owner_id).inputs().skip_binder().len() == 1 } + has_self && { + cx.tcx + .fn_sig(item.id.owner_id) + .skip_binder() + .inputs() + .skip_binder() + .len() + == 1 + } } else { false } @@ -224,7 +233,7 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, trait_items .any(|i| { i.kind == ty::AssocKind::Fn && i.fn_has_self_parameter - && cx.tcx.fn_sig(i.def_id).inputs().skip_binder().len() == 1 + && cx.tcx.fn_sig(i.def_id).skip_binder().inputs().skip_binder().len() == 1 }); if !is_empty_method_found { @@ -342,7 +351,11 @@ fn check_for_is_empty<'tcx>( ), Some(is_empty) if !(is_empty.fn_has_self_parameter - && check_is_empty_sig(cx.tcx.fn_sig(is_empty.def_id).skip_binder(), self_kind, output)) => + && check_is_empty_sig( + cx.tcx.fn_sig(is_empty.def_id).subst_identity().skip_binder(), + self_kind, + output, + )) => { ( format!( @@ -473,7 +486,7 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { /// Gets an `AssocItem` and return true if it matches `is_empty(self)`. fn is_is_empty(cx: &LateContext<'_>, item: &ty::AssocItem) -> bool { if item.kind == ty::AssocKind::Fn { - let sig = cx.tcx.fn_sig(item.def_id); + let sig = cx.tcx.fn_sig(item.def_id).skip_binder(); let ty = sig.skip_binder(); ty.inputs().len() == 1 } else { diff --git a/src/tools/clippy/clippy_lints/src/let_underscore.rs b/src/tools/clippy/clippy_lints/src/let_underscore.rs index 61f87b914..7600777fa 100644 --- a/src/tools/clippy/clippy_lints/src/let_underscore.rs +++ b/src/tools/clippy/clippy_lints/src/let_underscore.rs @@ -84,13 +84,51 @@ declare_clippy_lint! { /// let _ = foo().await; /// # } /// ``` - #[clippy::version = "1.66"] + #[clippy::version = "1.67.0"] pub LET_UNDERSCORE_FUTURE, suspicious, "non-binding `let` on a future" } -declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_FUTURE]); +declare_clippy_lint! { + /// ### What it does + /// Checks for `let _ = <expr>` without a type annotation, and suggests to either provide one, + /// or remove the `let` keyword altogether. + /// + /// ### Why is this bad? + /// The `let _ = <expr>` expression ignores the value of `<expr>` but will remain doing so even + /// if the type were to change, thus potentially introducing subtle bugs. By supplying a type + /// annotation, one will be forced to re-visit the decision to ignore the value in such cases. + /// + /// ### Known problems + /// The `_ = <expr>` is not properly supported by some tools (e.g. IntelliJ) and may seem odd + /// to many developers. This lint also partially overlaps with the other `let_underscore_*` + /// lints. + /// + /// ### Example + /// ```rust + /// fn foo() -> Result<u32, ()> { + /// Ok(123) + /// } + /// let _ = foo(); + /// ``` + /// Use instead: + /// ```rust + /// fn foo() -> Result<u32, ()> { + /// Ok(123) + /// } + /// // Either provide a type annotation: + /// let _: Result<u32, ()> = foo(); + /// // …or drop the let keyword: + /// _ = foo(); + /// ``` + #[clippy::version = "1.69.0"] + pub LET_UNDERSCORE_UNTYPED, + pedantic, + "non-binding `let` without a type annotation" +} + +declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_MUST_USE, LET_UNDERSCORE_LOCK, LET_UNDERSCORE_FUTURE, LET_UNDERSCORE_UNTYPED]); const SYNC_GUARD_PATHS: [&[&str]; 3] = [ &paths::PARKING_LOT_MUTEX_GUARD, @@ -148,6 +186,18 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore { "consider explicitly using function result", ); } + + if local.pat.default_binding_modes && local.ty.is_none() { + // When `default_binding_modes` is true, the `let` keyword is present. + span_lint_and_help( + cx, + LET_UNDERSCORE_UNTYPED, + local.span, + "non-binding `let` without a type annotation", + None, + "consider adding a type annotation or removing the `let` keyword", + ); + } } } } diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index e93f27bee..c626e0bd9 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -1,8 +1,8 @@ #![feature(array_windows)] #![feature(binary_heap_into_iter_sorted)] #![feature(box_patterns)] -#![feature(control_flow_enum)] #![feature(drain_filter)] +#![feature(if_let_guard)] #![feature(iter_intersperse)] #![feature(let_chains)] #![feature(lint_reasons)] @@ -43,6 +43,7 @@ extern crate rustc_session; extern crate rustc_span; extern crate rustc_target; extern crate rustc_trait_selection; +extern crate thin_vec; #[macro_use] extern crate clippy_utils; @@ -122,6 +123,7 @@ mod excessive_bools; mod exhaustive_items; mod exit; mod explicit_write; +mod extra_unused_type_parameters; mod fallible_impl_from; mod float_literal; mod floating_point_arithmetic; @@ -198,6 +200,7 @@ mod missing_trait_methods; mod mixed_read_write_in_expression; mod module_style; mod multi_assignments; +mod multiple_unsafe_ops_per_block; mod mut_key; mod mut_mut; mod mut_reference; @@ -217,6 +220,7 @@ mod neg_cmp_op_on_partial_ord; mod neg_multiply; mod new_without_default; mod no_effect; +mod no_mangle_with_rust_abi; mod non_copy_const; mod non_expressive_names; mod non_octal_unix_permissions; @@ -241,6 +245,7 @@ mod ptr; mod ptr_offset_with_cast; mod pub_use; mod question_mark; +mod question_mark_used; mod ranges; mod rc_clone_in_vec_init; mod read_zero_byte_vec; @@ -262,6 +267,7 @@ mod semicolon_block; mod semicolon_if_nothing_returned; mod serde_api; mod shadow; +mod significant_drop_tightening; mod single_char_lifetime_names; mod single_component_path_imports; mod size_of_in_element_count; @@ -557,6 +563,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(eta_reduction::EtaReduction)); store.register_late_pass(|_| Box::new(mut_mut::MutMut)); store.register_late_pass(|_| Box::new(mut_reference::UnnecessaryMutPassed)); + store.register_late_pass(|_| Box::<significant_drop_tightening::SignificantDropTightening<'_>>::default()); store.register_late_pass(|_| Box::new(len_zero::LenZero)); store.register_late_pass(|_| Box::new(attrs::Attributes)); store.register_late_pass(|_| Box::new(blocks_in_if_conditions::BlocksInIfConditions)); @@ -663,12 +670,13 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: )) }); let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::<FxHashSet<_>>(); + let missing_docs_in_crate_items = conf.missing_docs_in_crate_items; store.register_late_pass(move |_| Box::new(doc::DocMarkdown::new(doc_valid_idents.clone()))); store.register_late_pass(|_| Box::new(neg_multiply::NegMultiply)); store.register_late_pass(|_| Box::new(mem_forget::MemForget)); store.register_late_pass(|_| Box::new(let_if_seq::LetIfSeq)); store.register_late_pass(|_| Box::new(mixed_read_write_in_expression::EvalOrderDependence)); - store.register_late_pass(|_| Box::new(missing_doc::MissingDoc::new())); + store.register_late_pass(move |_| Box::new(missing_doc::MissingDoc::new(missing_docs_in_crate_items))); store.register_late_pass(|_| Box::new(missing_inline::MissingInline)); store.register_late_pass(move |_| Box::new(exhaustive_items::ExhaustiveItems)); store.register_late_pass(|_| Box::new(match_result_ok::MatchResultOk)); @@ -692,6 +700,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(implicit_hasher::ImplicitHasher)); store.register_late_pass(|_| Box::new(fallible_impl_from::FallibleImplFrom)); store.register_late_pass(|_| Box::new(question_mark::QuestionMark)); + store.register_late_pass(|_| Box::new(question_mark_used::QuestionMarkUsed)); store.register_early_pass(|| Box::new(suspicious_operation_groupings::SuspiciousOperationGroupings)); store.register_late_pass(|_| Box::new(suspicious_trait_impl::SuspiciousImpl)); store.register_late_pass(|_| Box::new(map_unit_fn::MapUnit)); @@ -908,6 +917,13 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(fn_null_check::FnNullCheck)); store.register_late_pass(|_| Box::new(permissions_set_readonly_false::PermissionsSetReadonlyFalse)); store.register_late_pass(|_| Box::new(size_of_ref::SizeOfRef)); + store.register_late_pass(|_| Box::new(multiple_unsafe_ops_per_block::MultipleUnsafeOpsPerBlock)); + store.register_late_pass(move |_| { + Box::new(extra_unused_type_parameters::ExtraUnusedTypeParameters::new( + avoid_breaking_exported_api, + )) + }); + store.register_late_pass(|_| Box::new(no_mangle_with_rust_abi::NoMangleWithRustAbi)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/src/tools/clippy/clippy_lints/src/lifetimes.rs b/src/tools/clippy/clippy_lints/src/lifetimes.rs index 7cf1a6b80..986ffcad8 100644 --- a/src/tools/clippy/clippy_lints/src/lifetimes.rs +++ b/src/tools/clippy/clippy_lints/src/lifetimes.rs @@ -1,21 +1,21 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::trait_ref_of_method; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::Applicability; use rustc_hir::intravisit::nested_filter::{self as hir_nested_filter, NestedFilter}; use rustc_hir::intravisit::{ - walk_fn_decl, walk_generic_arg, walk_generic_param, walk_generics, walk_impl_item_ref, walk_item, walk_param_bound, + walk_fn_decl, walk_generic_param, walk_generics, walk_impl_item_ref, walk_item, walk_param_bound, walk_poly_trait_ref, walk_trait_ref, walk_ty, Visitor, }; -use rustc_hir::lang_items; use rustc_hir::FnRetTy::Return; use rustc_hir::{ - BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, Impl, ImplItem, - ImplItemKind, Item, ItemKind, Lifetime, LifetimeName, LifetimeParamKind, PolyTraitRef, PredicateOrigin, TraitFn, - TraitItem, TraitItemKind, Ty, TyKind, WherePredicate, + lang_items, BareFnTy, BodyId, FnDecl, FnSig, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, + Impl, ImplItem, ImplItemKind, Item, ItemKind, Lifetime, LifetimeName, LifetimeParamKind, Node, PolyTraitRef, + PredicateOrigin, TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WherePredicate, }; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::nested_filter as middle_nested_filter; -use rustc_middle::ty::TyCtxt; +use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Span; @@ -34,8 +34,6 @@ declare_clippy_lint! { /// ### Known problems /// - We bail out if the function has a `where` clause where lifetimes /// are mentioned due to potential false positives. - /// - Lifetime bounds such as `impl Foo + 'a` and `T: 'a` must be elided with the - /// placeholder notation `'_` because the fully elided notation leaves the type bound to `'static`. /// /// ### Example /// ```rust @@ -93,7 +91,7 @@ declare_lint_pass!(Lifetimes => [NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES]); impl<'tcx> LateLintPass<'tcx> for Lifetimes { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Fn(ref sig, generics, id) = item.kind { - check_fn_inner(cx, sig.decl, Some(id), None, generics, item.span, true); + check_fn_inner(cx, sig, Some(id), None, generics, item.span, true); } else if let ItemKind::Impl(impl_) = item.kind { if !item.span.from_expansion() { report_extra_impl_lifetimes(cx, impl_); @@ -106,7 +104,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { let report_extra_lifetimes = trait_ref_of_method(cx, item.owner_id.def_id).is_none(); check_fn_inner( cx, - sig.decl, + sig, Some(id), None, item.generics, @@ -122,29 +120,21 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { TraitFn::Required(sig) => (None, Some(sig)), TraitFn::Provided(id) => (Some(id), None), }; - check_fn_inner(cx, sig.decl, body, trait_sig, item.generics, item.span, true); + check_fn_inner(cx, sig, body, trait_sig, item.generics, item.span, true); } } } -/// The lifetime of a &-reference. -#[derive(PartialEq, Eq, Hash, Debug, Clone)] -enum RefLt { - Unnamed, - Static, - Named(LocalDefId), -} - fn check_fn_inner<'tcx>( cx: &LateContext<'tcx>, - decl: &'tcx FnDecl<'_>, + sig: &'tcx FnSig<'_>, body: Option<BodyId>, trait_sig: Option<&[Ident]>, generics: &'tcx Generics<'_>, span: Span, report_extra_lifetimes: bool, ) { - if span.from_expansion() || has_where_lifetimes(cx, generics) { + if in_external_macro(cx.sess(), span) || has_where_lifetimes(cx, generics) { return; } @@ -154,7 +144,11 @@ fn check_fn_inner<'tcx>( .filter(|param| matches!(param.kind, GenericParamKind::Type { .. })); for typ in types { - for pred in generics.bounds_for_param(cx.tcx.hir().local_def_id(typ.hir_id)) { + if !typ.span.eq_ctxt(span) { + return; + } + + for pred in generics.bounds_for_param(typ.def_id) { if pred.origin == PredicateOrigin::WhereClause { // has_where_lifetimes checked that this predicate contains no lifetime. continue; @@ -163,7 +157,7 @@ fn check_fn_inner<'tcx>( for bound in pred.bounds { let mut visitor = RefVisitor::new(cx); walk_param_bound(&mut visitor, bound); - if visitor.lts.iter().any(|lt| matches!(lt, RefLt::Named(_))) { + if visitor.lts.iter().any(|lt| matches!(lt.res, LifetimeName::Param(_))) { return; } if let GenericBound::Trait(ref trait_ref, _) = *bound { @@ -190,12 +184,16 @@ fn check_fn_inner<'tcx>( } } - if let Some(elidable_lts) = could_use_elision(cx, decl, body, trait_sig, generics.params) { + if let Some((elidable_lts, usages)) = could_use_elision(cx, sig.decl, body, trait_sig, generics.params) { + if usages.iter().any(|usage| !usage.ident.span.eq_ctxt(span)) { + return; + } + let lts = elidable_lts .iter() // In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a // `Node::GenericParam`. - .filter_map(|&(def_id, _)| cx.tcx.hir().get_by_def_id(def_id).ident()) + .filter_map(|&def_id| cx.tcx.hir().get_by_def_id(def_id).ident()) .map(|ident| ident.to_string()) .collect::<Vec<_>>() .join(", "); @@ -203,21 +201,99 @@ fn check_fn_inner<'tcx>( span_lint_and_then( cx, NEEDLESS_LIFETIMES, - span.with_hi(decl.output.span().hi()), + span.with_hi(sig.decl.output.span().hi()), &format!("the following explicit lifetimes could be elided: {lts}"), |diag| { - if let Some(span) = elidable_lts.iter().find_map(|&(_, span)| span) { - diag.span_help(span, "replace with `'_` in generic arguments such as here"); + if sig.header.is_async() { + // async functions have usages whose spans point at the lifetime declaration which messes up + // suggestions + return; + }; + + if let Some(suggestions) = elision_suggestions(cx, generics, &elidable_lts, &usages) { + diag.multipart_suggestion("elide the lifetimes", suggestions, Applicability::MachineApplicable); } }, ); } if report_extra_lifetimes { - self::report_extra_lifetimes(cx, decl, generics); + self::report_extra_lifetimes(cx, sig.decl, generics); } } +fn elision_suggestions( + cx: &LateContext<'_>, + generics: &Generics<'_>, + elidable_lts: &[LocalDefId], + usages: &[Lifetime], +) -> Option<Vec<(Span, String)>> { + let explicit_params = generics + .params + .iter() + .filter(|param| !param.is_elided_lifetime() && !param.is_impl_trait()) + .collect::<Vec<_>>(); + + let mut suggestions = if elidable_lts.len() == explicit_params.len() { + // if all the params are elided remove the whole generic block + // + // fn x<'a>() {} + // ^^^^ + vec![(generics.span, String::new())] + } else { + elidable_lts + .iter() + .map(|&id| { + let pos = explicit_params.iter().position(|param| param.def_id == id)?; + let param = explicit_params.get(pos)?; + + let span = if let Some(next) = explicit_params.get(pos + 1) { + // fn x<'prev, 'a, 'next>() {} + // ^^^^ + param.span.until(next.span) + } else { + // `pos` should be at least 1 here, because the param in position 0 would either have a `next` + // param or would have taken the `elidable_lts.len() == explicit_params.len()` branch. + let prev = explicit_params.get(pos - 1)?; + + // fn x<'prev, 'a>() {} + // ^^^^ + param.span.with_lo(prev.span.hi()) + }; + + Some((span, String::new())) + }) + .collect::<Option<Vec<_>>>()? + }; + + suggestions.extend( + usages + .iter() + .filter(|usage| named_lifetime(usage).map_or(false, |id| elidable_lts.contains(&id))) + .map(|usage| { + match cx.tcx.hir().get_parent(usage.hir_id) { + Node::Ty(Ty { + kind: TyKind::Ref(..), .. + }) => { + // expand `&'a T` to `&'a T` + // ^^ ^^^ + let span = cx + .sess() + .source_map() + .span_extend_while(usage.ident.span, |ch| ch.is_ascii_whitespace()) + .unwrap_or(usage.ident.span); + + (span, String::new()) + }, + // `T<'a>` and `impl Foo + 'a` should be replaced by `'_` + _ => (usage.ident.span, String::from("'_")), + } + }), + ); + + Some(suggestions) +} + // elision doesn't work for explicit self types, see rust-lang/rust#69064 fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option<Ident>) -> bool { if_chain! { @@ -237,13 +313,20 @@ fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: } } +fn named_lifetime(lt: &Lifetime) -> Option<LocalDefId> { + match lt.res { + LifetimeName::Param(id) if !lt.is_anonymous() => Some(id), + _ => None, + } +} + fn could_use_elision<'tcx>( cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>, body: Option<BodyId>, trait_sig: Option<&[Ident]>, named_generics: &'tcx [GenericParam<'_>], -) -> Option<Vec<(LocalDefId, Option<Span>)>> { +) -> Option<(Vec<LocalDefId>, Vec<Lifetime>)> { // There are two scenarios where elision works: // * no output references, all input references have different LT // * output references, exactly one input reference with same LT @@ -251,7 +334,7 @@ fn could_use_elision<'tcx>( // level of the current item. // check named LTs - let allowed_lts = allowed_lts_from(cx.tcx, named_generics); + let allowed_lts = allowed_lts_from(named_generics); // these will collect all the lifetimes for references in arg/return types let mut input_visitor = RefVisitor::new(cx); @@ -301,32 +384,24 @@ fn could_use_elision<'tcx>( // check for lifetimes from higher scopes for lt in input_lts.iter().chain(output_lts.iter()) { - if !allowed_lts.contains(lt) { + if let Some(id) = named_lifetime(lt) + && !allowed_lts.contains(&id) + { return None; } } // check for higher-ranked trait bounds if !input_visitor.nested_elision_site_lts.is_empty() || !output_visitor.nested_elision_site_lts.is_empty() { - let allowed_lts: FxHashSet<_> = allowed_lts - .iter() - .filter_map(|lt| match lt { - RefLt::Named(def_id) => Some(cx.tcx.item_name(def_id.to_def_id())), - _ => None, - }) - .collect(); + let allowed_lts: FxHashSet<_> = allowed_lts.iter().map(|id| cx.tcx.item_name(id.to_def_id())).collect(); for lt in input_visitor.nested_elision_site_lts { - if let RefLt::Named(def_id) = lt { - if allowed_lts.contains(&cx.tcx.item_name(def_id.to_def_id())) { - return None; - } + if allowed_lts.contains(<.ident.name) { + return None; } } for lt in output_visitor.nested_elision_site_lts { - if let RefLt::Named(def_id) = lt { - if allowed_lts.contains(&cx.tcx.item_name(def_id.to_def_id())) { - return None; - } + if allowed_lts.contains(<.ident.name) { + return None; } } } @@ -338,15 +413,10 @@ fn could_use_elision<'tcx>( let elidable_lts = named_lifetime_occurrences(&input_lts) .into_iter() .filter_map(|(def_id, occurrences)| { - if occurrences == 1 && (input_lts.len() == 1 || !output_lts.contains(&RefLt::Named(def_id))) { - Some(( - def_id, - input_visitor - .lifetime_generic_arg_spans - .get(&def_id) - .or_else(|| output_visitor.lifetime_generic_arg_spans.get(&def_id)) - .copied(), - )) + if occurrences == 1 + && (input_lts.len() == 1 || !output_lts.iter().any(|lt| named_lifetime(lt) == Some(def_id))) + { + Some(def_id) } else { None } @@ -354,31 +424,34 @@ fn could_use_elision<'tcx>( .collect::<Vec<_>>(); if elidable_lts.is_empty() { - None - } else { - Some(elidable_lts) + return None; } + + let usages = itertools::chain(input_lts, output_lts).collect(); + + Some((elidable_lts, usages)) } -fn allowed_lts_from(tcx: TyCtxt<'_>, named_generics: &[GenericParam<'_>]) -> FxHashSet<RefLt> { - let mut allowed_lts = FxHashSet::default(); - for par in named_generics.iter() { - if let GenericParamKind::Lifetime { .. } = par.kind { - allowed_lts.insert(RefLt::Named(tcx.hir().local_def_id(par.hir_id))); - } - } - allowed_lts.insert(RefLt::Unnamed); - allowed_lts.insert(RefLt::Static); - allowed_lts +fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxHashSet<LocalDefId> { + named_generics + .iter() + .filter_map(|par| { + if let GenericParamKind::Lifetime { .. } = par.kind { + Some(par.def_id) + } else { + None + } + }) + .collect() } /// Number of times each named lifetime occurs in the given slice. Returns a vector to preserve /// relative order. #[must_use] -fn named_lifetime_occurrences(lts: &[RefLt]) -> Vec<(LocalDefId, usize)> { +fn named_lifetime_occurrences(lts: &[Lifetime]) -> Vec<(LocalDefId, usize)> { let mut occurrences = Vec::new(); for lt in lts { - if let &RefLt::Named(curr_def_id) = lt { + if let Some(curr_def_id) = named_lifetime(lt) { if let Some(pair) = occurrences .iter_mut() .find(|(prev_def_id, _)| *prev_def_id == curr_def_id) @@ -392,12 +465,10 @@ fn named_lifetime_occurrences(lts: &[RefLt]) -> Vec<(LocalDefId, usize)> { occurrences } -/// A visitor usable for `rustc_front::visit::walk_ty()`. struct RefVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, - lts: Vec<RefLt>, - lifetime_generic_arg_spans: FxHashMap<LocalDefId, Span>, - nested_elision_site_lts: Vec<RefLt>, + lts: Vec<Lifetime>, + nested_elision_site_lts: Vec<Lifetime>, unelided_trait_object_lifetime: bool, } @@ -406,32 +477,16 @@ impl<'a, 'tcx> RefVisitor<'a, 'tcx> { Self { cx, lts: Vec::new(), - lifetime_generic_arg_spans: FxHashMap::default(), nested_elision_site_lts: Vec::new(), unelided_trait_object_lifetime: false, } } - fn record(&mut self, lifetime: &Option<Lifetime>) { - if let Some(ref lt) = *lifetime { - if lt.is_static() { - self.lts.push(RefLt::Static); - } else if lt.is_anonymous() { - // Fresh lifetimes generated should be ignored. - self.lts.push(RefLt::Unnamed); - } else if let LifetimeName::Param(def_id) = lt.res { - self.lts.push(RefLt::Named(def_id)); - } - } else { - self.lts.push(RefLt::Unnamed); - } - } - - fn all_lts(&self) -> Vec<RefLt> { + fn all_lts(&self) -> Vec<Lifetime> { self.lts .iter() .chain(self.nested_elision_site_lts.iter()) - .cloned() + .copied() .collect::<Vec<_>>() } @@ -443,7 +498,7 @@ impl<'a, 'tcx> RefVisitor<'a, 'tcx> { impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> { // for lifetimes as parameters of generics fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) { - self.record(&Some(*lifetime)); + self.lts.push(*lifetime); } fn visit_poly_trait_ref(&mut self, poly_tref: &'tcx PolyTraitRef<'tcx>) { @@ -468,11 +523,7 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> { walk_item(self, item); self.lts.truncate(len); self.lts.extend(bounds.iter().filter_map(|bound| match bound { - GenericArg::Lifetime(l) => Some(if let LifetimeName::Param(def_id) = l.res { - RefLt::Named(def_id) - } else { - RefLt::Unnamed - }), + GenericArg::Lifetime(&l) => Some(l), _ => None, })); }, @@ -492,13 +543,6 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> { _ => walk_ty(self, ty), } } - - fn visit_generic_arg(&mut self, generic_arg: &'tcx GenericArg<'tcx>) { - if let GenericArg::Lifetime(l) = generic_arg && let LifetimeName::Param(def_id) = l.res { - self.lifetime_generic_arg_spans.entry(def_id).or_insert(l.ident.span); - } - walk_generic_arg(self, generic_arg); - } } /// Are any lifetimes mentioned in the `where` clause? If so, we don't try to @@ -516,14 +560,18 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_ return true; } // if the bounds define new lifetimes, they are fine to occur - let allowed_lts = allowed_lts_from(cx.tcx, pred.bound_generic_params); + let allowed_lts = allowed_lts_from(pred.bound_generic_params); // now walk the bounds for bound in pred.bounds.iter() { walk_param_bound(&mut visitor, bound); } // and check that all lifetimes are allowed - if visitor.all_lts().iter().any(|it| !allowed_lts.contains(it)) { - return true; + for lt in visitor.all_lts() { + if let Some(id) = named_lifetime(<) + && !allowed_lts.contains(&id) + { + return true; + } } }, WherePredicate::EqPredicate(ref pred) => { diff --git a/src/tools/clippy/clippy_lints/src/literal_representation.rs b/src/tools/clippy/clippy_lints/src/literal_representation.rs index 3a7b7835c..dadcd9c51 100644 --- a/src/tools/clippy/clippy_lints/src/literal_representation.rs +++ b/src/tools/clippy/clippy_lints/src/literal_representation.rs @@ -210,7 +210,7 @@ impl WarningType { cx, UNUSUAL_BYTE_GROUPINGS, span, - "digits of hex or binary literal not grouped by four", + "digits of hex, binary or octal literal not in groups of equal size", "consider", suggested_format, Applicability::MachineApplicable, @@ -427,8 +427,12 @@ impl LiteralDigitGrouping { let first = groups.next().expect("At least one group"); - if (radix == Radix::Binary || radix == Radix::Hexadecimal) && groups.any(|i| i != 4 && i != 2) { - return Err(WarningType::UnusualByteGroupings); + if radix == Radix::Binary || radix == Radix::Octal || radix == Radix::Hexadecimal { + if let Some(second_size) = groups.next() { + if !groups.all(|i| i == second_size) || first > second_size { + return Err(WarningType::UnusualByteGroupings); + } + } } if let Some(second) = groups.next() { @@ -484,7 +488,7 @@ impl DecimalLiteralRepresentation { then { let hex = format!("{val:#X}"); let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false); - let _ = Self::do_lint(num_lit.integer).map_err(|warning_type| { + let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| { warning_type.display(num_lit.format(), cx, span); }); } diff --git a/src/tools/clippy/clippy_lints/src/loops/explicit_iter_loop.rs b/src/tools/clippy/clippy_lints/src/loops/explicit_iter_loop.rs index b1f294162..151c7f1d5 100644 --- a/src/tools/clippy/clippy_lints/src/loops/explicit_iter_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/explicit_iter_loop.rs @@ -68,7 +68,7 @@ fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool { // IntoIterator is currently only implemented for array sizes <= 32 in rustc match ty.kind() { ty::Array(_, n) => n - .try_eval_usize(cx.tcx, cx.param_env) + .try_eval_target_usize(cx.tcx, cx.param_env) .map_or(false, |val| (0..=32).contains(&val)), _ => false, } diff --git a/src/tools/clippy/clippy_lints/src/loops/mod.rs b/src/tools/clippy/clippy_lints/src/loops/mod.rs index 8e52cac43..610a0233e 100644 --- a/src/tools/clippy/clippy_lints/src/loops/mod.rs +++ b/src/tools/clippy/clippy_lints/src/loops/mod.rs @@ -61,7 +61,8 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// Just iterating the collection itself makes the intent - /// more clear and is probably faster. + /// more clear and is probably faster because it eliminates + /// the bounds check that is done when indexing. /// /// ### Example /// ```rust diff --git a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs index 3bca93d80..5c317c2a5 100644 --- a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs @@ -149,7 +149,7 @@ pub(super) fn check<'tcx>( |diag| { multispan_sugg( diag, - "consider using an iterator", + "consider using an iterator and enumerate()", vec![ (pat.span, format!("({}, <item>)", ident.name)), ( @@ -211,7 +211,7 @@ fn is_end_eq_array_len<'tcx>( if let ExprKind::Lit(ref lit) = end.kind; if let ast::LitKind::Int(end_int, _) = lit.node; if let ty::Array(_, arr_len_const) = indexed_ty.kind(); - if let Some(arr_len) = arr_len_const.try_eval_usize(cx.tcx, cx.param_env); + if let Some(arr_len) = arr_len_const.try_eval_target_usize(cx.tcx, cx.param_env); then { return match limits { ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(), @@ -370,7 +370,7 @@ impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> { ExprKind::MethodCall(_, receiver, args, _) => { let def_id = self.cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap(); for (ty, expr) in iter::zip( - self.cx.tcx.fn_sig(def_id).inputs().skip_binder(), + self.cx.tcx.fn_sig(def_id).subst_identity().inputs().skip_binder(), std::iter::once(receiver).chain(args.iter()), ) { self.prefer_mutable = false; diff --git a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs index 14f161f51..b1bc10802 100644 --- a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs @@ -39,6 +39,7 @@ pub(super) fn check( }); }, NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (), + NeverLoopResult::IgnoreUntilEnd(_) => unreachable!(), } } @@ -48,6 +49,8 @@ enum NeverLoopResult { AlwaysBreak, // A continue may occur for the main loop. MayContinueMainLoop, + // Ignore everything until the end of the block with this id + IgnoreUntilEnd(HirId), Otherwise, } @@ -56,6 +59,7 @@ fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult { match arg { NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise, NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop, + NeverLoopResult::IgnoreUntilEnd(id) => NeverLoopResult::IgnoreUntilEnd(id), } } @@ -63,27 +67,26 @@ fn absorb_break(arg: NeverLoopResult) -> NeverLoopResult { #[must_use] fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult { match first { - NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop => first, - NeverLoopResult::Otherwise => second, - } -} - -// Combine two results where both parts are called but not necessarily in order. -#[must_use] -fn combine_both(left: NeverLoopResult, right: NeverLoopResult) -> NeverLoopResult { - match (left, right) { - (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => { - NeverLoopResult::MayContinueMainLoop + NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop | NeverLoopResult::IgnoreUntilEnd(_) => { + first }, - (NeverLoopResult::AlwaysBreak, _) | (_, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak, - (NeverLoopResult::Otherwise, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise, + NeverLoopResult::Otherwise => second, } } // Combine two results where only one of the part may have been executed. #[must_use] -fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult { +fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult, ignore_ids: &[HirId]) -> NeverLoopResult { match (b1, b2) { + (NeverLoopResult::IgnoreUntilEnd(a), NeverLoopResult::IgnoreUntilEnd(b)) => { + if ignore_ids.iter().find(|&e| e == &a || e == &b).unwrap() == &a { + NeverLoopResult::IgnoreUntilEnd(b) + } else { + NeverLoopResult::IgnoreUntilEnd(a) + } + }, + (i @ NeverLoopResult::IgnoreUntilEnd(_), NeverLoopResult::AlwaysBreak) + | (NeverLoopResult::AlwaysBreak, i @ NeverLoopResult::IgnoreUntilEnd(_)) => i, (NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak, (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => { NeverLoopResult::MayContinueMainLoop @@ -103,7 +106,7 @@ fn never_loop_block(block: &Block<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id let e = never_loop_expr(e, ignore_ids, main_loop_id); // els is an else block in a let...else binding els.map_or(e, |els| { - combine_branches(e, never_loop_block(els, ignore_ids, main_loop_id)) + combine_branches(e, never_loop_block(els, ignore_ids, main_loop_id), ignore_ids) }) }) .fold(NeverLoopResult::Otherwise, combine_seq) @@ -139,7 +142,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H ExprKind::Struct(_, fields, base) => { let fields = never_loop_expr_all(&mut fields.iter().map(|f| f.expr), ignore_ids, main_loop_id); if let Some(base) = base { - combine_both(fields, never_loop_expr(base, ignore_ids, main_loop_id)) + combine_seq(fields, never_loop_expr(base, ignore_ids, main_loop_id)) } else { fields } @@ -159,7 +162,7 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H let e3 = e3.as_ref().map_or(NeverLoopResult::Otherwise, |e| { never_loop_expr(e, ignore_ids, main_loop_id) }); - combine_seq(e1, combine_branches(e2, e3)) + combine_seq(e1, combine_branches(e2, e3, ignore_ids)) }, ExprKind::Match(e, arms, _) => { let e = never_loop_expr(e, ignore_ids, main_loop_id); @@ -175,8 +178,13 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H ignore_ids.push(b.hir_id); } let ret = never_loop_block(b, ignore_ids, main_loop_id); - ignore_ids.pop(); - ret + if l.is_some() { + ignore_ids.pop(); + } + match ret { + NeverLoopResult::IgnoreUntilEnd(a) if a == b.hir_id => NeverLoopResult::Otherwise, + _ => ret, + } }, ExprKind::Continue(d) => { let id = d @@ -190,8 +198,8 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H }, // checks if break targets a block instead of a loop ExprKind::Break(Destination { target_id: Ok(t), .. }, e) if ignore_ids.contains(&t) => e - .map_or(NeverLoopResult::Otherwise, |e| { - combine_seq(never_loop_expr(e, ignore_ids, main_loop_id), NeverLoopResult::Otherwise) + .map_or(NeverLoopResult::IgnoreUntilEnd(t), |e| { + never_loop_expr(e, ignore_ids, main_loop_id) }), ExprKind::Break(_, e) | ExprKind::Ret(e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| { combine_seq( @@ -218,13 +226,13 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H | InlineAsmOperand::SymFn { .. } | InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise, }) - .fold(NeverLoopResult::Otherwise, combine_both), + .fold(NeverLoopResult::Otherwise, combine_seq), ExprKind::Yield(_, _) | ExprKind::Closure { .. } | ExprKind::Path(_) | ExprKind::ConstBlock(_) | ExprKind::Lit(_) - | ExprKind::Err => NeverLoopResult::Otherwise, + | ExprKind::Err(_) => NeverLoopResult::Otherwise, } } @@ -234,7 +242,7 @@ fn never_loop_expr_all<'a, T: Iterator<Item = &'a Expr<'a>>>( main_loop_id: HirId, ) -> NeverLoopResult { es.map(|e| never_loop_expr(e, ignore_ids, main_loop_id)) - .fold(NeverLoopResult::Otherwise, combine_both) + .fold(NeverLoopResult::Otherwise, combine_seq) } fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>( @@ -242,8 +250,9 @@ fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>( ignore_ids: &mut Vec<HirId>, main_loop_id: HirId, ) -> NeverLoopResult { - e.map(|e| never_loop_expr(e, ignore_ids, main_loop_id)) - .fold(NeverLoopResult::AlwaysBreak, combine_branches) + e.fold(NeverLoopResult::AlwaysBreak, |a, b| { + combine_branches(a, never_loop_expr(b, ignore_ids, main_loop_id), ignore_ids) + }) } fn for_to_if_let_sugg(cx: &LateContext<'_>, iterator: &Expr<'_>, pat: &Pat<'_>) -> String { diff --git a/src/tools/clippy/clippy_lints/src/manual_assert.rs b/src/tools/clippy/clippy_lints/src/manual_assert.rs index 4277455a3..ce5d657bc 100644 --- a/src/tools/clippy/clippy_lints/src/manual_assert.rs +++ b/src/tools/clippy/clippy_lints/src/manual_assert.rs @@ -1,7 +1,6 @@ use crate::rustc_lint::LintContext; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::macros::{root_macro_call, FormatArgsExpn}; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::macros::root_macro_call; use clippy_utils::{is_else_clause, peel_blocks_with_stmt, span_extract_comment, sugg}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, UnOp}; @@ -38,57 +37,57 @@ declare_lint_pass!(ManualAssert => [MANUAL_ASSERT]); impl<'tcx> LateLintPass<'tcx> for ManualAssert { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { - if_chain! { - if let ExprKind::If(cond, then, None) = expr.kind; - if !matches!(cond.kind, ExprKind::Let(_)); - if !expr.span.from_expansion(); - let then = peel_blocks_with_stmt(then); - if let Some(macro_call) = root_macro_call(then.span); - if cx.tcx.item_name(macro_call.def_id) == sym::panic; - if !cx.tcx.sess.source_map().is_multiline(cond.span); - if let Some(format_args) = FormatArgsExpn::find_nested(cx, then, macro_call.expn); + if let ExprKind::If(cond, then, None) = expr.kind + && !matches!(cond.kind, ExprKind::Let(_)) + && !expr.span.from_expansion() + && let then = peel_blocks_with_stmt(then) + && let Some(macro_call) = root_macro_call(then.span) + && cx.tcx.item_name(macro_call.def_id) == sym::panic + && !cx.tcx.sess.source_map().is_multiline(cond.span) + && let Ok(panic_snippet) = cx.sess().source_map().span_to_snippet(macro_call.span) + && let Some(panic_snippet) = panic_snippet.strip_suffix(')') + && let Some((_, format_args_snip)) = panic_snippet.split_once('(') // Don't change `else if foo { panic!(..) }` to `else { assert!(foo, ..) }` as it just // shuffles the condition around. // Should this have a config value? - if !is_else_clause(cx.tcx, expr); - then { - let mut applicability = Applicability::MachineApplicable; - let format_args_snip = snippet_with_applicability(cx, format_args.inputs_span(), "..", &mut applicability); - let cond = cond.peel_drop_temps(); - let mut comments = span_extract_comment(cx.sess().source_map(), expr.span); - if !comments.is_empty() { - comments += "\n"; - } - let (cond, not) = match cond.kind { - ExprKind::Unary(UnOp::Not, e) => (e, ""), - _ => (cond, "!"), - }; - let cond_sugg = sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_par(); - let sugg = format!("assert!({not}{cond_sugg}, {format_args_snip});"); - // we show to the user the suggestion without the comments, but when applicating the fix, include the comments in the block - span_lint_and_then( - cx, - MANUAL_ASSERT, - expr.span, - "only a `panic!` in `if`-then statement", - |diag| { - // comments can be noisy, do not show them to the user - if !comments.is_empty() { - diag.tool_only_span_suggestion( - expr.span.shrink_to_lo(), - "add comments back", - comments, - applicability); - } - diag.span_suggestion( - expr.span, - "try instead", - sugg, - applicability); - } - - ); + && !is_else_clause(cx.tcx, expr) + { + let mut applicability = Applicability::MachineApplicable; + let cond = cond.peel_drop_temps(); + let mut comments = span_extract_comment(cx.sess().source_map(), expr.span); + if !comments.is_empty() { + comments += "\n"; } + let (cond, not) = match cond.kind { + ExprKind::Unary(UnOp::Not, e) => (e, ""), + _ => (cond, "!"), + }; + let cond_sugg = sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_par(); + let sugg = format!("assert!({not}{cond_sugg}, {format_args_snip});"); + // we show to the user the suggestion without the comments, but when applicating the fix, include the comments in the block + span_lint_and_then( + cx, + MANUAL_ASSERT, + expr.span, + "only a `panic!` in `if`-then statement", + |diag| { + // comments can be noisy, do not show them to the user + if !comments.is_empty() { + diag.tool_only_span_suggestion( + expr.span.shrink_to_lo(), + "add comments back", + comments, + applicability + ); + } + diag.span_suggestion( + expr.span, + "try instead", + sugg, + applicability + ); + } + ); } } } diff --git a/src/tools/clippy/clippy_lints/src/manual_async_fn.rs b/src/tools/clippy/clippy_lints/src/manual_async_fn.rs index 63212beaa..3778eb4c7 100644 --- a/src/tools/clippy/clippy_lints/src/manual_async_fn.rs +++ b/src/tools/clippy/clippy_lints/src/manual_async_fn.rs @@ -6,10 +6,11 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{ AsyncGeneratorKind, Block, Body, Closure, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericArg, GenericBound, - HirId, ItemKind, LifetimeName, Term, TraitRef, Ty, TyKind, TypeBindingKind, + ItemKind, LifetimeName, Term, TraitRef, Ty, TyKind, TypeBindingKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::def_id::LocalDefId; use rustc_span::{sym, Span}; declare_clippy_lint! { @@ -45,7 +46,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn { decl: &'tcx FnDecl<'_>, body: &'tcx Body<'_>, span: Span, - _: HirId, + _: LocalDefId, ) { if_chain! { if let Some(header) = kind.header(); diff --git a/src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs b/src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs index d9ef7dffa..2fd32c009 100644 --- a/src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs +++ b/src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs @@ -43,7 +43,7 @@ declare_clippy_lint! { /// 'A'.is_ascii_uppercase(); /// } /// ``` - #[clippy::version = "1.66.0"] + #[clippy::version = "1.67.0"] pub MANUAL_IS_ASCII_CHECK, style, "use dedicated method to check ascii range" diff --git a/src/tools/clippy/clippy_lints/src/manual_let_else.rs b/src/tools/clippy/clippy_lints/src/manual_let_else.rs index 9c6f8b43c..98e698c6c 100644 --- a/src/tools/clippy/clippy_lints/src/manual_let_else.rs +++ b/src/tools/clippy/clippy_lints/src/manual_let_else.rs @@ -4,11 +4,12 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::peel_blocks; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::visitors::{for_each_expr, Descend}; +use clippy_utils::visitors::{Descend, Visitable}; use if_chain::if_chain; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind}; +use rustc_hir::intravisit::{walk_expr, Visitor}; +use rustc_hir::{Expr, ExprKind, HirId, ItemId, Local, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind, Ty}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_tool_lint, impl_lint_pass}; @@ -115,6 +116,13 @@ impl<'tcx> LateLintPass<'tcx> for ManualLetElse { .enumerate() .find(|(_, arm)| expr_diverges(cx, arm.body) && pat_allowed_for_else(cx, arm.pat, check_types)); let Some((idx, diverging_arm)) = diverging_arm_opt else { return; }; + // If the non-diverging arm is the first one, its pattern can be reused in a let/else statement. + // However, if it arrives in second position, its pattern may cover some cases already covered + // by the diverging one. + // TODO: accept the non-diverging arm as a second position if patterns are disjointed. + if idx == 0 { + return; + } let pat_arm = &arms[1 - idx]; if !expr_is_simple_identity(pat_arm.pat, pat_arm.body) { return; @@ -162,61 +170,102 @@ fn emit_manual_let_else(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, pat: ); } -fn expr_diverges(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool { - fn is_never(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool { - if let Some(ty) = cx.typeck_results().expr_ty_opt(expr) { - return ty.is_never(); - } - false +/// Check whether an expression is divergent. May give false negatives. +fn expr_diverges(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + struct V<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + res: ControlFlow<(), Descend>, } - // We can't just call is_never on expr and be done, because the type system - // sometimes coerces the ! type to something different before we can get - // our hands on it. So instead, we do a manual search. We do fall back to - // is_never in some places when there is no better alternative. - for_each_expr(expr, |ex| { - match ex.kind { - ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => ControlFlow::Break(()), - ExprKind::Call(call, _) => { - if is_never(cx, ex) || is_never(cx, call) { - return ControlFlow::Break(()); - } - ControlFlow::Continue(Descend::Yes) - }, - ExprKind::MethodCall(..) => { - if is_never(cx, ex) { - return ControlFlow::Break(()); - } - ControlFlow::Continue(Descend::Yes) - }, - ExprKind::If(if_expr, if_then, if_else) => { - let else_diverges = if_else.map_or(false, |ex| expr_diverges(cx, ex)); - let diverges = expr_diverges(cx, if_expr) || (else_diverges && expr_diverges(cx, if_then)); - if diverges { - return ControlFlow::Break(()); + impl<'tcx> Visitor<'tcx> for V<'_, '_> { + fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) { + fn is_never(cx: &LateContext<'_>, expr: &'_ Expr<'_>) -> bool { + if let Some(ty) = cx.typeck_results().expr_ty_opt(expr) { + return ty.is_never(); } - ControlFlow::Continue(Descend::No) - }, - ExprKind::Match(match_expr, match_arms, _) => { - let diverges = expr_diverges(cx, match_expr) - || match_arms.iter().all(|arm| { - let guard_diverges = arm.guard.as_ref().map_or(false, |g| expr_diverges(cx, g.body())); - guard_diverges || expr_diverges(cx, arm.body) - }); - if diverges { - return ControlFlow::Break(()); - } - ControlFlow::Continue(Descend::No) - }, + false + } - // Don't continue into loops or labeled blocks, as they are breakable, - // and we'd have to start checking labels. - ExprKind::Block(_, Some(_)) | ExprKind::Loop(..) => ControlFlow::Continue(Descend::No), + if self.res.is_break() { + return; + } - // Default: descend - _ => ControlFlow::Continue(Descend::Yes), + // We can't just call is_never on expr and be done, because the type system + // sometimes coerces the ! type to something different before we can get + // our hands on it. So instead, we do a manual search. We do fall back to + // is_never in some places when there is no better alternative. + self.res = match e.kind { + ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => ControlFlow::Break(()), + ExprKind::Call(call, _) => { + if is_never(self.cx, e) || is_never(self.cx, call) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(Descend::Yes) + } + }, + ExprKind::MethodCall(..) => { + if is_never(self.cx, e) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(Descend::Yes) + } + }, + ExprKind::If(if_expr, if_then, if_else) => { + let else_diverges = if_else.map_or(false, |ex| expr_diverges(self.cx, ex)); + let diverges = + expr_diverges(self.cx, if_expr) || (else_diverges && expr_diverges(self.cx, if_then)); + if diverges { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(Descend::No) + } + }, + ExprKind::Match(match_expr, match_arms, _) => { + let diverges = expr_diverges(self.cx, match_expr) + || match_arms.iter().all(|arm| { + let guard_diverges = arm.guard.as_ref().map_or(false, |g| expr_diverges(self.cx, g.body())); + guard_diverges || expr_diverges(self.cx, arm.body) + }); + if diverges { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(Descend::No) + } + }, + + // Don't continue into loops or labeled blocks, as they are breakable, + // and we'd have to start checking labels. + ExprKind::Block(_, Some(_)) | ExprKind::Loop(..) => ControlFlow::Continue(Descend::No), + + // Default: descend + _ => ControlFlow::Continue(Descend::Yes), + }; + if let ControlFlow::Continue(Descend::Yes) = self.res { + walk_expr(self, e); + } + } + + fn visit_local(&mut self, local: &'tcx Local<'_>) { + // Don't visit the else block of a let/else statement as it will not make + // the statement divergent even though the else block is divergent. + if let Some(init) = local.init { + self.visit_expr(init); + } } - }) - .is_some() + + // Avoid unnecessary `walk_*` calls. + fn visit_ty(&mut self, _: &'tcx Ty<'tcx>) {} + fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {} + fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {} + // Avoid monomorphising all `visit_*` functions. + fn visit_nested_item(&mut self, _: ItemId) {} + } + + let mut v = V { + cx, + res: ControlFlow::Continue(Descend::Yes), + }; + expr.visit(&mut v); + v.res.is_break() } fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: bool) -> bool { diff --git a/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs b/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs index bca193be9..9a84068d4 100644 --- a/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs +++ b/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs @@ -157,11 +157,10 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum { && def.variants.len() > 1 { let mut iter = def.variants.iter().filter_map(|v| { - let id = cx.tcx.hir().local_def_id(v.hir_id); - (matches!(v.data, hir::VariantData::Unit(..)) + (matches!(v.data, hir::VariantData::Unit(_, _)) && v.ident.as_str().starts_with('_') && is_doc_hidden(cx.tcx.hir().attrs(v.hir_id))) - .then_some((id, v.span)) + .then_some((v.def_id, v.span)) }); if let Some((id, span)) = iter.next() && iter.next().is_none() diff --git a/src/tools/clippy/clippy_lints/src/map_unit_fn.rs b/src/tools/clippy/clippy_lints/src/map_unit_fn.rs index 59195d1ae..edcab6968 100644 --- a/src/tools/clippy/clippy_lints/src/map_unit_fn.rs +++ b/src/tools/clippy/clippy_lints/src/map_unit_fn.rs @@ -104,7 +104,7 @@ fn is_unit_function(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { let ty = cx.typeck_results().expr_ty(expr); if let ty::FnDef(id, _) = *ty.kind() { - if let Some(fn_type) = cx.tcx.fn_sig(id).no_bound_vars() { + if let Some(fn_type) = cx.tcx.fn_sig(id).subst_identity().no_bound_vars() { return is_unit_type(fn_type.output()); } } diff --git a/src/tools/clippy/clippy_lints/src/matches/match_wild_enum.rs b/src/tools/clippy/clippy_lints/src/matches/match_wild_enum.rs index 59de8c038..3126b5901 100644 --- a/src/tools/clippy/clippy_lints/src/matches/match_wild_enum.rs +++ b/src/tools/clippy/clippy_lints/src/matches/match_wild_enum.rs @@ -45,8 +45,13 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { // Accumulate the variants which should be put in place of the wildcard because they're not // already covered. - let has_hidden = adt_def.variants().iter().any(|x| is_hidden(cx, x)); - let mut missing_variants: Vec<_> = adt_def.variants().iter().filter(|x| !is_hidden(cx, x)).collect(); + let is_external = adt_def.did().as_local().is_none(); + let has_external_hidden = is_external && adt_def.variants().iter().any(|x| is_hidden(cx, x)); + let mut missing_variants: Vec<_> = adt_def + .variants() + .iter() + .filter(|x| !(is_external && is_hidden(cx, x))) + .collect(); let mut path_prefix = CommonPrefixSearcher::None; for arm in arms { @@ -133,7 +138,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { match missing_variants.as_slice() { [] => (), - [x] if !adt_def.is_variant_list_non_exhaustive() && !has_hidden => span_lint_and_sugg( + [x] if !adt_def.is_variant_list_non_exhaustive() && !has_external_hidden => span_lint_and_sugg( cx, MATCH_WILDCARD_FOR_SINGLE_VARIANTS, wildcard_span, @@ -144,7 +149,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { ), variants => { let mut suggestions: Vec<_> = variants.iter().copied().map(format_suggestion).collect(); - let message = if adt_def.is_variant_list_non_exhaustive() || has_hidden { + let message = if adt_def.is_variant_list_non_exhaustive() || has_external_hidden { suggestions.push("_".into()); "wildcard matches known variants and will also match future added variants" } else { diff --git a/src/tools/clippy/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs b/src/tools/clippy/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs index 0aadb482a..d06bcdaa2 100644 --- a/src/tools/clippy/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs +++ b/src/tools/clippy/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs @@ -10,7 +10,7 @@ pub(crate) fn check(cx: &LateContext<'_>, pat: &Pat<'_>) { if !pat.span.from_expansion(); if let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind; if let Some(def_id) = path.res.opt_def_id(); - let ty = cx.tcx.type_of(def_id); + let ty = cx.tcx.type_of(def_id).subst_identity(); if let ty::Adt(def, _) = ty.kind(); if def.is_struct() || def.is_union(); if fields.len() == def.non_enum_variant().fields.len(); diff --git a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index f587c69f7..b33a24781 100644 --- a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -341,7 +341,7 @@ impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> { ExprKind::ConstBlock(_) | ExprKind::Continue(_) | ExprKind::DropTemps(_) | - ExprKind::Err | + ExprKind::Err(_) | ExprKind::InlineAsm(_) | ExprKind::Let(_) | ExprKind::Lit(_) | diff --git a/src/tools/clippy/clippy_lints/src/methods/bytes_count_to_len.rs b/src/tools/clippy/clippy_lints/src/methods/bytes_count_to_len.rs index 89aaad359..46a20ad41 100644 --- a/src/tools/clippy/clippy_lints/src/methods/bytes_count_to_len.rs +++ b/src/tools/clippy/clippy_lints/src/methods/bytes_count_to_len.rs @@ -17,7 +17,7 @@ pub(super) fn check<'tcx>( if_chain! { if let Some(bytes_id) = cx.typeck_results().type_dependent_def_id(count_recv.hir_id); if let Some(impl_id) = cx.tcx.impl_of_method(bytes_id); - if cx.tcx.type_of(impl_id).is_str(); + if cx.tcx.type_of(impl_id).subst_identity().is_str(); let ty = cx.typeck_results().expr_ty(bytes_recv).peel_refs(); if ty.is_str() || is_type_lang_item(cx, ty, hir::LangItem::String); then { diff --git a/src/tools/clippy/clippy_lints/src/methods/bytes_nth.rs b/src/tools/clippy/clippy_lints/src/methods/bytes_nth.rs index d512cc4ee..c5fc145b2 100644 --- a/src/tools/clippy/clippy_lints/src/methods/bytes_nth.rs +++ b/src/tools/clippy/clippy_lints/src/methods/bytes_nth.rs @@ -5,6 +5,8 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, LangItem}; use rustc_lint::LateContext; +use crate::methods::method_call; + use super::BYTES_NTH; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, n_arg: &'tcx Expr<'tcx>) { @@ -16,18 +18,32 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx E } else { return; }; + let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - BYTES_NTH, - expr.span, - &format!("called `.bytes().nth()` on a `{caller_type}`"), - "try", - format!( - "{}.as_bytes().get({})", - snippet_with_applicability(cx, recv.span, "..", &mut applicability), - snippet_with_applicability(cx, n_arg.span, "..", &mut applicability) - ), - applicability, - ); + let receiver = snippet_with_applicability(cx, recv.span, "..", &mut applicability); + let n = snippet_with_applicability(cx, n_arg.span, "..", &mut applicability); + + if let Some(parent) = clippy_utils::get_parent_expr(cx, expr) + && let Some((name, _, _, _, _)) = method_call(parent) + && name == "unwrap" { + span_lint_and_sugg( + cx, + BYTES_NTH, + parent.span, + &format!("called `.bytes().nth().unwrap()` on a `{caller_type}`"), + "try", + format!("{receiver}.as_bytes()[{n}]",), + applicability + ); + } else { + span_lint_and_sugg( + cx, + BYTES_NTH, + expr.span, + &format!("called `.bytes().nth()` on a `{caller_type}`"), + "try", + format!("{receiver}.as_bytes().get({n}).copied()"), + applicability + ); + }; } diff --git a/src/tools/clippy/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs b/src/tools/clippy/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs index 0b3bf2274..7711aa78b 100644 --- a/src/tools/clippy/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs +++ b/src/tools/clippy/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs @@ -30,7 +30,7 @@ pub(super) fn check<'tcx>( if_chain! { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); if let Some(impl_id) = cx.tcx.impl_of_method(method_id); - if cx.tcx.type_of(impl_id).is_str(); + if cx.tcx.type_of(impl_id).subst_identity().is_str(); if let ExprKind::Lit(Spanned { node: LitKind::Str(ext_literal, ..), ..}) = arg.kind; if (2..=6).contains(&ext_literal.as_str().len()); let ext_str = ext_literal.as_str(); diff --git a/src/tools/clippy/clippy_lints/src/methods/collapsible_str_replace.rs b/src/tools/clippy/clippy_lints/src/methods/collapsible_str_replace.rs index ac61b4377..5e01ed90f 100644 --- a/src/tools/clippy/clippy_lints/src/methods/collapsible_str_replace.rs +++ b/src/tools/clippy/clippy_lints/src/methods/collapsible_str_replace.rs @@ -54,7 +54,7 @@ fn collect_replace_calls<'tcx>( from_args.push_front(from); ControlFlow::Continue(()) } else { - ControlFlow::BREAK + ControlFlow::Break(()) } } else { ControlFlow::Continue(()) diff --git a/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs index a9189b31c..a22285058 100644 --- a/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs +++ b/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs @@ -33,7 +33,7 @@ pub(super) fn check<'tcx>( if (method_name.ident.name == sym::as_str || method_name.ident.name == sym::as_ref) && { let arg_type = cx.typeck_results().expr_ty(receiver); let base_type = arg_type.peel_refs(); - *base_type.kind() == ty::Str || is_type_lang_item(cx, base_type, hir::LangItem::String) + base_type.is_str() || is_type_lang_item(cx, base_type, hir::LangItem::String) } { receiver } else { @@ -54,7 +54,7 @@ pub(super) fn check<'tcx>( return false; } if let ty::Ref(_, ty, ..) = arg_ty.kind() { - if *ty.kind() == ty::Str && can_be_static_str(cx, arg) { + if ty.is_str() && can_be_static_str(cx, arg) { return false; } }; @@ -70,7 +70,7 @@ pub(super) fn check<'tcx>( if let hir::ExprKind::Path(ref p) = fun.kind { match cx.qpath_res(p, fun.hir_id) { hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!( - cx.tcx.fn_sig(def_id).output().skip_binder().kind(), + cx.tcx.fn_sig(def_id).subst_identity().output().skip_binder().kind(), ty::Ref(re, ..) if re.is_static(), ), _ => false, @@ -84,7 +84,7 @@ pub(super) fn check<'tcx>( .type_dependent_def_id(arg.hir_id) .map_or(false, |method_id| { matches!( - cx.tcx.fn_sig(method_id).output().skip_binder().kind(), + cx.tcx.fn_sig(method_id).subst_identity().output().skip_binder().kind(), ty::Ref(re, ..) if re.is_static() ) }) diff --git a/src/tools/clippy/clippy_lints/src/methods/expect_used.rs b/src/tools/clippy/clippy_lints/src/methods/expect_used.rs index cce8f797e..614610335 100644 --- a/src/tools/clippy/clippy_lints/src/methods/expect_used.rs +++ b/src/tools/clippy/clippy_lints/src/methods/expect_used.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::is_in_cfg_test; use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{is_in_cfg_test, is_in_test_function}; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; @@ -27,7 +27,7 @@ pub(super) fn check( let method = if is_err { "expect_err" } else { "expect" }; - if allow_expect_in_tests && is_in_cfg_test(cx.tcx, expr.hir_id) { + if allow_expect_in_tests && (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id)) { return; } diff --git a/src/tools/clippy/clippy_lints/src/methods/get_first.rs b/src/tools/clippy/clippy_lints/src/methods/get_first.rs index cb17af608..945bbf53b 100644 --- a/src/tools/clippy/clippy_lints/src/methods/get_first.rs +++ b/src/tools/clippy/clippy_lints/src/methods/get_first.rs @@ -19,7 +19,7 @@ pub(super) fn check<'tcx>( if_chain! { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); if let Some(impl_id) = cx.tcx.impl_of_method(method_id); - if cx.tcx.type_of(impl_id).is_slice(); + if cx.tcx.type_of(impl_id).subst_identity().is_slice(); if let Some(_) = is_slice_of_primitives(cx, recv); if let hir::ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = arg.kind; then { diff --git a/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs b/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs index 06ecbce4e..5a78a4168 100644 --- a/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs +++ b/src/tools/clippy/clippy_lints/src/methods/implicit_clone.rs @@ -53,7 +53,9 @@ pub fn is_clone_like(cx: &LateContext<'_>, method_name: &str, method_def_id: hir "to_vec" => cx .tcx .impl_of_method(method_def_id) - .filter(|&impl_did| cx.tcx.type_of(impl_did).is_slice() && cx.tcx.impl_trait_ref(impl_did).is_none()) + .filter(|&impl_did| { + cx.tcx.type_of(impl_did).subst_identity().is_slice() && cx.tcx.impl_trait_ref(impl_did).is_none() + }) .is_some(), _ => false, } diff --git a/src/tools/clippy/clippy_lints/src/methods/manual_ok_or.rs b/src/tools/clippy/clippy_lints/src/methods/manual_ok_or.rs index 5b758f1e6..b9a0ec779 100644 --- a/src/tools/clippy/clippy_lints/src/methods/manual_ok_or.rs +++ b/src/tools/clippy/clippy_lints/src/methods/manual_ok_or.rs @@ -21,7 +21,7 @@ pub(super) fn check<'tcx>( if_chain! { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); if let Some(impl_id) = cx.tcx.impl_of_method(method_id); - if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::Option); + if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).subst_identity(), sym::Option); if let ExprKind::Call(err_path, [err_arg]) = or_expr.kind; if is_res_lang_ctor(cx, path_res(cx, err_path), ResultErr); if is_ok_wrapping(cx, map_expr); diff --git a/src/tools/clippy/clippy_lints/src/methods/map_clone.rs b/src/tools/clippy/clippy_lints/src/methods/map_clone.rs index 52cc1e046..2b26ef014 100644 --- a/src/tools/clippy/clippy_lints/src/methods/map_clone.rs +++ b/src/tools/clippy/clippy_lints/src/methods/map_clone.rs @@ -19,7 +19,7 @@ pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_ if_chain! { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id); if cx.tcx.impl_of_method(method_id) - .map_or(false, |id| is_type_diagnostic_item(cx, cx.tcx.type_of(id), sym::Option)) + .map_or(false, |id| is_type_diagnostic_item(cx, cx.tcx.type_of(id).subst_identity(), sym::Option)) || is_diag_trait_item(cx, method_id, sym::Iterator); if let hir::ExprKind::Closure(&hir::Closure{ body, .. }) = arg.kind; then { diff --git a/src/tools/clippy/clippy_lints/src/methods/map_err_ignore.rs b/src/tools/clippy/clippy_lints/src/methods/map_err_ignore.rs index b773b3e42..a5beb291f 100644 --- a/src/tools/clippy/clippy_lints/src/methods/map_err_ignore.rs +++ b/src/tools/clippy/clippy_lints/src/methods/map_err_ignore.rs @@ -9,7 +9,7 @@ use super::MAP_ERR_IGNORE; pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, arg: &Expr<'_>) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) && let Some(impl_id) = cx.tcx.impl_of_method(method_id) - && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::Result) + && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).subst_identity(), sym::Result) && let ExprKind::Closure(&Closure { capture_clause: CaptureBy::Ref, body, diff --git a/src/tools/clippy/clippy_lints/src/methods/mod.rs b/src/tools/clippy/clippy_lints/src/methods/mod.rs index 77be61b47..702df4b28 100644 --- a/src/tools/clippy/clippy_lints/src/methods/mod.rs +++ b/src/tools/clippy/clippy_lints/src/methods/mod.rs @@ -80,6 +80,7 @@ mod skip_while_next; mod stable_sort_primitive; mod str_splitn; mod string_extend_chars; +mod suspicious_command_arg_space; mod suspicious_map; mod suspicious_splitn; mod suspicious_to_owned; @@ -1818,6 +1819,7 @@ declare_clippy_lint! { /// - `or_else` to `or` /// - `get_or_insert_with` to `get_or_insert` /// - `ok_or_else` to `ok_or` + /// - `then` to `then_some` (for msrv >= 1.62.0) /// /// ### Why is this bad? /// Using eager evaluation is shorter and simpler in some cases. @@ -3102,7 +3104,7 @@ declare_clippy_lint! { /// Ok(()) /// } /// ``` - #[clippy::version = "1.66.0"] + #[clippy::version = "1.67.0"] pub SEEK_FROM_CURRENT, complexity, "use dedicated method for seek from current position" @@ -3133,7 +3135,7 @@ declare_clippy_lint! { /// t.rewind(); /// } /// ``` - #[clippy::version = "1.66.0"] + #[clippy::version = "1.67.0"] pub SEEK_TO_START_INSTEAD_OF_REWIND, complexity, "jumping to the start of stream using `seek` method" @@ -3161,6 +3163,32 @@ declare_clippy_lint! { "collecting an iterator when collect is not needed" } +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `Command::arg()` invocations that look like they + /// should be multiple arguments instead, such as `arg("-t ext2")`. + /// + /// ### Why is this bad? + /// + /// `Command::arg()` does not split arguments by space. An argument like `arg("-t ext2")` + /// will be passed as a single argument to the command, + /// which is likely not what was intended. + /// + /// ### Example + /// ```rust + /// std::process::Command::new("echo").arg("-n hello").spawn().unwrap(); + /// ``` + /// Use instead: + /// ```rust + /// std::process::Command::new("echo").args(["-n", "hello"]).spawn().unwrap(); + /// ``` + #[clippy::version = "1.67.0"] + pub SUSPICIOUS_COMMAND_ARG_SPACE, + suspicious, + "single command line argument that looks like it should be multiple arguments" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -3288,6 +3316,7 @@ impl_lint_pass!(Methods => [ SEEK_FROM_CURRENT, SEEK_TO_START_INSTEAD_OF_REWIND, NEEDLESS_COLLECT, + SUSPICIOUS_COMMAND_ARG_SPACE, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -3348,11 +3377,11 @@ impl<'tcx> LateLintPass<'tcx> for Methods { let name = impl_item.ident.name.as_str(); let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id()).def_id; let item = cx.tcx.hir().expect_item(parent); - let self_ty = cx.tcx.type_of(item.owner_id); + let self_ty = cx.tcx.type_of(item.owner_id).subst_identity(); let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. })); if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind { - let method_sig = cx.tcx.fn_sig(impl_item.owner_id); + let method_sig = cx.tcx.fn_sig(impl_item.owner_id).subst_identity(); let method_sig = cx.tcx.erase_late_bound_regions(method_sig); let first_arg_ty_opt = method_sig.inputs().iter().next().copied(); // if this impl block implements a trait, lint in trait definition instead @@ -3412,7 +3441,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { } if let hir::ImplItemKind::Fn(_, _) = impl_item.kind { - let ret_ty = return_ty(cx, impl_item.hir_id()); + let ret_ty = return_ty(cx, impl_item.owner_id); if contains_ty_adt_constructor_opaque(cx, ret_ty, self_ty) { return; @@ -3460,7 +3489,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if_chain! { if item.ident.name == sym::new; if let TraitItemKind::Fn(_, _) = item.kind; - let ret_ty = return_ty(cx, item.hir_id()); + let ret_ty = return_ty(cx, item.owner_id); let self_ty = TraitRef::identity(cx.tcx, item.owner_id.to_def_id()) .self_ty() .skip_binder(); @@ -3495,6 +3524,9 @@ impl Methods { unnecessary_lazy_eval::check(cx, expr, recv, arg, "and"); } }, + ("arg", [arg]) => { + suspicious_command_arg_space::check(cx, recv, arg, span); + } ("as_deref" | "as_deref_mut", []) => { needless_option_as_deref::check(cx, expr, recv, name); }, diff --git a/src/tools/clippy/clippy_lints/src/methods/mut_mutex_lock.rs b/src/tools/clippy/clippy_lints/src/methods/mut_mutex_lock.rs index b9593b368..d0aa39d06 100644 --- a/src/tools/clippy/clippy_lints/src/methods/mut_mutex_lock.rs +++ b/src/tools/clippy/clippy_lints/src/methods/mut_mutex_lock.rs @@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>, recv: &' if let ty::Ref(_, _, Mutability::Mut) = cx.typeck_results().expr_ty(recv).kind(); if let Some(method_id) = cx.typeck_results().type_dependent_def_id(ex.hir_id); if let Some(impl_id) = cx.tcx.impl_of_method(method_id); - if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::Mutex); + if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).subst_identity(), sym::Mutex); then { span_lint_and_sugg( cx, diff --git a/src/tools/clippy/clippy_lints/src/methods/needless_collect.rs b/src/tools/clippy/clippy_lints/src/methods/needless_collect.rs index f4d3ef3b7..0b0c6adc5 100644 --- a/src/tools/clippy/clippy_lints/src/methods/needless_collect.rs +++ b/src/tools/clippy/clippy_lints/src/methods/needless_collect.rs @@ -137,7 +137,7 @@ pub(super) fn check<'tcx>( /// Checks if the given method call matches the expected signature of `([&[mut]] self) -> bool` fn is_is_empty_sig(cx: &LateContext<'_>, call_id: HirId) -> bool { cx.typeck_results().type_dependent_def_id(call_id).map_or(false, |id| { - let sig = cx.tcx.fn_sig(id).skip_binder(); + let sig = cx.tcx.fn_sig(id).subst_identity().skip_binder(); sig.inputs().len() == 1 && sig.output().is_bool() }) } @@ -165,7 +165,7 @@ fn iterates_same_ty<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx>, collect_ty: fn is_contains_sig(cx: &LateContext<'_>, call_id: HirId, iter_expr: &Expr<'_>) -> bool { let typeck = cx.typeck_results(); if let Some(id) = typeck.type_dependent_def_id(call_id) - && let sig = cx.tcx.fn_sig(id) + && let sig = cx.tcx.fn_sig(id).subst_identity() && sig.skip_binder().output().is_bool() && let [_, search_ty] = *sig.skip_binder().inputs() && let ty::Ref(_, search_ty, Mutability::Not) = *cx.tcx.erase_late_bound_regions(sig.rebind(search_ty)).kind() @@ -173,7 +173,7 @@ fn is_contains_sig(cx: &LateContext<'_>, call_id: HirId, iter_expr: &Expr<'_>) - && let Some(iter_item) = cx.tcx .associated_items(iter_trait) .find_by_name_and_kind(cx.tcx, Ident::with_dummy_span(Symbol::intern("Item")), AssocKind::Type, iter_trait) - && let substs = cx.tcx.mk_substs([GenericArg::from(typeck.expr_ty_adjusted(iter_expr))].into_iter()) + && let substs = cx.tcx.mk_substs(&[GenericArg::from(typeck.expr_ty_adjusted(iter_expr))]) && let proj_ty = cx.tcx.mk_projection(iter_item.def_id, substs) && let Ok(item_ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, proj_ty) { diff --git a/src/tools/clippy/clippy_lints/src/methods/open_options.rs b/src/tools/clippy/clippy_lints/src/methods/open_options.rs index 597af853d..c6a27cdd6 100644 --- a/src/tools/clippy/clippy_lints/src/methods/open_options.rs +++ b/src/tools/clippy/clippy_lints/src/methods/open_options.rs @@ -11,7 +11,7 @@ use super::NONSENSICAL_OPEN_OPTIONS; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) && let Some(impl_id) = cx.tcx.impl_of_method(method_id) - && match_type(cx, cx.tcx.type_of(impl_id), &paths::OPEN_OPTIONS) + && match_type(cx, cx.tcx.type_of(impl_id).subst_identity(), &paths::OPEN_OPTIONS) { let mut options = Vec::new(); get_open_options(cx, recv, &mut options); diff --git a/src/tools/clippy/clippy_lints/src/methods/path_buf_push_overwrite.rs b/src/tools/clippy/clippy_lints/src/methods/path_buf_push_overwrite.rs index 0cc28c0dc..e3f2de3cd 100644 --- a/src/tools/clippy/clippy_lints/src/methods/path_buf_push_overwrite.rs +++ b/src/tools/clippy/clippy_lints/src/methods/path_buf_push_overwrite.rs @@ -14,7 +14,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t if_chain! { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); if let Some(impl_id) = cx.tcx.impl_of_method(method_id); - if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::PathBuf); + if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).subst_identity(), sym::PathBuf); if let ExprKind::Lit(ref lit) = arg.kind; if let LitKind::Str(ref path_lit, _) = lit.node; if let pushed_path = Path::new(path_lit.as_str()); diff --git a/src/tools/clippy/clippy_lints/src/methods/search_is_some.rs b/src/tools/clippy/clippy_lints/src/methods/search_is_some.rs index 1c031ad6a..afdb8ce94 100644 --- a/src/tools/clippy/clippy_lints/src/methods/search_is_some.rs +++ b/src/tools/clippy/clippy_lints/src/methods/search_is_some.rs @@ -8,7 +8,6 @@ use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::PatKind; use rustc_lint::LateContext; -use rustc_middle::ty; use rustc_span::source_map::Span; use rustc_span::symbol::sym; @@ -108,7 +107,7 @@ pub(super) fn check<'tcx>( if is_type_lang_item(cx, self_ty, hir::LangItem::String) { true } else { - *self_ty.kind() == ty::Str + self_ty.is_str() } }; if_chain! { diff --git a/src/tools/clippy/clippy_lints/src/methods/single_char_pattern.rs b/src/tools/clippy/clippy_lints/src/methods/single_char_pattern.rs index 4221c52d5..4d704ec39 100644 --- a/src/tools/clippy/clippy_lints/src/methods/single_char_pattern.rs +++ b/src/tools/clippy/clippy_lints/src/methods/single_char_pattern.rs @@ -47,7 +47,7 @@ pub(super) fn check( for &(method, pos) in &PATTERN_METHODS { if_chain! { if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(receiver).kind(); - if *ty.kind() == ty::Str; + if ty.is_str(); if method_name.as_str() == method && args.len() > pos; let arg = &args[pos]; let mut applicability = Applicability::MachineApplicable; diff --git a/src/tools/clippy/clippy_lints/src/methods/stable_sort_primitive.rs b/src/tools/clippy/clippy_lints/src/methods/stable_sort_primitive.rs index 09c8ca4cb..b5fd0ad8c 100644 --- a/src/tools/clippy/clippy_lints/src/methods/stable_sort_primitive.rs +++ b/src/tools/clippy/clippy_lints/src/methods/stable_sort_primitive.rs @@ -10,7 +10,7 @@ use super::STABLE_SORT_PRIMITIVE; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) && let Some(impl_id) = cx.tcx.impl_of_method(method_id) - && cx.tcx.type_of(impl_id).is_slice() + && cx.tcx.type_of(impl_id).subst_identity().is_slice() && let Some(slice_type) = is_slice_of_primitives(cx, recv) { span_lint_and_then( diff --git a/src/tools/clippy/clippy_lints/src/methods/string_extend_chars.rs b/src/tools/clippy/clippy_lints/src/methods/string_extend_chars.rs index f35d81cee..2c20c6d75 100644 --- a/src/tools/clippy/clippy_lints/src/methods/string_extend_chars.rs +++ b/src/tools/clippy/clippy_lints/src/methods/string_extend_chars.rs @@ -5,7 +5,6 @@ use clippy_utils::ty::is_type_lang_item; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; -use rustc_middle::ty; use super::STRING_EXTEND_CHARS; @@ -17,7 +16,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr if let Some(arglists) = method_chain_args(arg, &["chars"]) { let target = &arglists[0].0; let self_ty = cx.typeck_results().expr_ty(target).peel_refs(); - let ref_str = if *self_ty.kind() == ty::Str { + let ref_str = if self_ty.is_str() { if matches!(target.kind, hir::ExprKind::Index(..)) { "&" } else { diff --git a/src/tools/clippy/clippy_lints/src/methods/suspicious_command_arg_space.rs b/src/tools/clippy/clippy_lints/src/methods/suspicious_command_arg_space.rs new file mode 100644 index 000000000..73632c5a3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/suspicious_command_arg_space.rs @@ -0,0 +1,39 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::paths; +use clippy_utils::ty::match_type; +use rustc_ast as ast; +use rustc_errors::{Applicability, Diagnostic}; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::Span; + +use super::SUSPICIOUS_COMMAND_ARG_SPACE; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>, span: Span) { + let ty = cx.typeck_results().expr_ty(recv).peel_refs(); + + if match_type(cx, ty, &paths::STD_PROCESS_COMMAND) + && let hir::ExprKind::Lit(lit) = &arg.kind + && let ast::LitKind::Str(s, _) = &lit.node + && let Some((arg1, arg2)) = s.as_str().split_once(' ') + && arg1.starts_with('-') + && arg1.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-') + { + span_lint_and_then( + cx, + SUSPICIOUS_COMMAND_ARG_SPACE, + arg.span, + "single argument that looks like it should be multiple arguments", + |diag: &mut Diagnostic| { + diag.multipart_suggestion_verbose( + "consider splitting the argument", + vec![ + (span, "args".to_string()), + (arg.span, format!("[{arg1:?}, {arg2:?}]")), + ], + Applicability::MaybeIncorrect, + ); + } + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/suspicious_map.rs b/src/tools/clippy/clippy_lints/src/methods/suspicious_map.rs index 2ac0786b3..0dc7fe2a2 100644 --- a/src/tools/clippy/clippy_lints/src/methods/suspicious_map.rs +++ b/src/tools/clippy/clippy_lints/src/methods/suspicious_map.rs @@ -11,10 +11,8 @@ use super::SUSPICIOUS_MAP; pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, count_recv: &hir::Expr<'_>, map_arg: &hir::Expr<'_>) { if_chain! { if is_trait_method(cx, count_recv, sym::Iterator); - let closure = expr_or_init(cx, map_arg); - if let Some(def_id) = cx.tcx.hir().opt_local_def_id(closure.hir_id); - if let Some(body_id) = cx.tcx.hir().maybe_body_owned_by(def_id); - let closure_body = cx.tcx.hir().body(body_id); + if let hir::ExprKind::Closure(closure) = expr_or_init(cx, map_arg).kind; + let closure_body = cx.tcx.hir().body(closure.body); if !cx.typeck_results().expr_ty(closure_body.value).is_unit(); then { if let Some(map_mutated_vars) = mutated_variables(closure_body.value, cx) { diff --git a/src/tools/clippy/clippy_lints/src/methods/suspicious_splitn.rs b/src/tools/clippy/clippy_lints/src/methods/suspicious_splitn.rs index 219a9edd6..90ca66bd7 100644 --- a/src/tools/clippy/clippy_lints/src/methods/suspicious_splitn.rs +++ b/src/tools/clippy/clippy_lints/src/methods/suspicious_splitn.rs @@ -13,7 +13,7 @@ pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, se if let Some(call_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); if let Some(impl_id) = cx.tcx.impl_of_method(call_id); if cx.tcx.impl_trait_ref(impl_id).is_none(); - let self_ty = cx.tcx.type_of(impl_id); + let self_ty = cx.tcx.type_of(impl_id).subst_identity(); if self_ty.is_slice() || self_ty.is_str(); then { // Ignore empty slice and string literals when used with a literal count. diff --git a/src/tools/clippy/clippy_lints/src/methods/suspicious_to_owned.rs b/src/tools/clippy/clippy_lints/src/methods/suspicious_to_owned.rs index fe88fa41f..e818f1892 100644 --- a/src/tools/clippy/clippy_lints/src/methods/suspicious_to_owned.rs +++ b/src/tools/clippy/clippy_lints/src/methods/suspicious_to_owned.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_diag_trait_item; use clippy_utils::source::snippet_with_context; use if_chain::if_chain; @@ -17,19 +17,31 @@ pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) - let input_type = cx.typeck_results().expr_ty(expr); if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(expr).kind(); if cx.tcx.is_diagnostic_item(sym::Cow, adt.did()); + then { let mut app = Applicability::MaybeIncorrect; let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0; - span_lint_and_sugg( + span_lint_and_then( cx, SUSPICIOUS_TO_OWNED, expr.span, &with_forced_trimmed_paths!(format!( "this `to_owned` call clones the {input_type} itself and does not cause the {input_type} contents to become owned" )), - "consider using, depending on intent", - format!("{recv_snip}.clone()` or `{recv_snip}.into_owned()"), - app, + |diag| { + diag.span_suggestion( + expr.span, + "depending on intent, either make the Cow an Owned variant", + format!("{recv_snip}.into_owned()"), + app + ); + diag.span_suggestion( + expr.span, + "or clone the Cow itself", + format!("{recv_snip}.clone()"), + app + ); + } ); return true; } diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_sort_by.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_sort_by.rs index ed5a75b0f..5201da52b 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_sort_by.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -122,7 +122,7 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Exp if_chain! { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); if let Some(impl_id) = cx.tcx.impl_of_method(method_id); - if cx.tcx.type_of(impl_id).is_slice(); + if cx.tcx.type_of(impl_id).subst_identity().is_slice(); if let ExprKind::Closure(&Closure { body, .. }) = arg.kind; if let closure_body = cx.tcx.hir().body(body); if let &[ diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs index 9263f0519..df26b36b7 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -246,7 +246,7 @@ fn check_other_call_arg<'tcx>( if_chain! { if let Some((maybe_call, maybe_arg)) = skip_addr_of_ancestors(cx, expr); if let Some((callee_def_id, _, recv, call_args)) = get_callee_substs_and_args(cx, maybe_call); - let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder(); + let fn_sig = cx.tcx.fn_sig(callee_def_id).subst_identity().skip_binder(); if let Some(i) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == maybe_arg.hir_id); if let Some(input) = fn_sig.inputs().get(i); let (input, n_refs) = peel_mid_ty_refs(*input); @@ -368,10 +368,9 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty< Node::Block(..) => continue, Node::Item(item) => { if let ItemKind::Fn(_, _, body_id) = &item.kind - && let output_ty = return_ty(cx, item.hir_id()) - && let local_def_id = cx.tcx.hir().local_def_id(item.hir_id()) - && Inherited::build(cx.tcx, local_def_id).enter(|inherited| { - let fn_ctxt = FnCtxt::new(inherited, cx.param_env, item.hir_id()); + && let output_ty = return_ty(cx, item.owner_id) + && Inherited::build(cx.tcx, item.owner_id.def_id).enter(|inherited| { + let fn_ctxt = FnCtxt::new(inherited, cx.param_env, item.owner_id.def_id); fn_ctxt.can_coerce(ty, output_ty) }) { if has_lifetime(output_ty) && has_lifetime(ty) { @@ -386,7 +385,7 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty< Node::Expr(parent_expr) => { if let Some((callee_def_id, call_substs, recv, call_args)) = get_callee_substs_and_args(cx, parent_expr) { - let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder(); + let fn_sig = cx.tcx.fn_sig(callee_def_id).subst_identity().skip_binder(); if let Some(arg_index) = recv.into_iter().chain(call_args).position(|arg| arg.hir_id == expr.hir_id) && let Some(param_ty) = fn_sig.inputs().get(arg_index) && let ty::Param(ParamTy { index: param_index , ..}) = param_ty.kind() @@ -415,7 +414,7 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty< } }); - let new_subst = cx.tcx.mk_substs( + let new_subst = cx.tcx.mk_substs_from_iter( call_substs.iter() .enumerate() .map(|(i, t)| diff --git a/src/tools/clippy/clippy_lints/src/methods/unwrap_used.rs b/src/tools/clippy/clippy_lints/src/methods/unwrap_used.rs index 90983f249..5e4c3daee 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unwrap_used.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unwrap_used.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_in_cfg_test, is_lint_allowed}; +use clippy_utils::{is_in_cfg_test, is_in_test_function, is_lint_allowed}; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_span::sym; @@ -27,7 +27,7 @@ pub(super) fn check( let method_suffix = if is_err { "_err" } else { "" }; - if allow_unwrap_in_tests && is_in_cfg_test(cx.tcx, expr.hir_id) { + if allow_unwrap_in_tests && (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id)) { return; } diff --git a/src/tools/clippy/clippy_lints/src/methods/utils.rs b/src/tools/clippy/clippy_lints/src/methods/utils.rs index ae6b165fd..c96d69226 100644 --- a/src/tools/clippy/clippy_lints/src/methods/utils.rs +++ b/src/tools/clippy/clippy_lints/src/methods/utils.rs @@ -22,7 +22,7 @@ pub(super) fn derefs_to_slice<'tcx>( ty::Slice(_) => true, ty::Adt(def, _) if def.is_box() => may_slice(cx, ty.boxed_ty()), ty::Adt(..) => is_type_diagnostic_item(cx, ty, sym::Vec), - ty::Array(_, size) => size.try_eval_usize(cx.tcx, cx.param_env).is_some(), + ty::Array(_, size) => size.try_eval_target_usize(cx.tcx, cx.param_env).is_some(), ty::Ref(_, inner, _) => may_slice(cx, *inner), _ => false, } @@ -143,7 +143,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> { if_chain! { if args.iter().all(|arg| !self.is_binding(arg)); if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id); - let method_ty = self.cx.tcx.type_of(method_def_id); + let method_ty = self.cx.tcx.type_of(method_def_id).subst_identity(); let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder(); if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not)); then { diff --git a/src/tools/clippy/clippy_lints/src/methods/vec_resize_to_zero.rs b/src/tools/clippy/clippy_lints/src/methods/vec_resize_to_zero.rs index 02d8364cb..b0cfc163f 100644 --- a/src/tools/clippy/clippy_lints/src/methods/vec_resize_to_zero.rs +++ b/src/tools/clippy/clippy_lints/src/methods/vec_resize_to_zero.rs @@ -20,7 +20,7 @@ pub(super) fn check<'tcx>( if_chain! { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); if let Some(impl_id) = cx.tcx.impl_of_method(method_id); - if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id), sym::Vec); + if is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).subst_identity(), sym::Vec); if let ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = count_arg.kind; if let ExprKind::Lit(Spanned { node: LitKind::Int(..), .. }) = default_arg.kind; then { diff --git a/src/tools/clippy/clippy_lints/src/misc.rs b/src/tools/clippy/clippy_lints/src/misc.rs index 9f4beb92b..0705029a6 100644 --- a/src/tools/clippy/clippy_lints/src/misc.rs +++ b/src/tools/clippy/clippy_lints/src/misc.rs @@ -4,12 +4,13 @@ use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{ - self as hir, def, BinOpKind, BindingAnnotation, Body, ByRef, Expr, ExprKind, FnDecl, HirId, Mutability, PatKind, - Stmt, StmtKind, TyKind, + self as hir, def, BinOpKind, BindingAnnotation, Body, ByRef, Expr, ExprKind, FnDecl, Mutability, PatKind, Stmt, + StmtKind, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::def_id::LocalDefId; use rustc_span::hygiene::DesugaringKind; use rustc_span::source_map::{ExpnKind, Span}; @@ -151,7 +152,7 @@ impl<'tcx> LateLintPass<'tcx> for LintPass { decl: &'tcx FnDecl<'_>, body: &'tcx Body<'_>, span: Span, - _: HirId, + _: LocalDefId, ) { if let FnKind::Closure = k { // Does not apply to closures diff --git a/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs index 5bc04bc17..87bd007a2 100644 --- a/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs +++ b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs @@ -6,11 +6,12 @@ use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, is_from_proc_ma use rustc_hir as hir; use rustc_hir::def_id::CRATE_DEF_ID; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, Constness, FnDecl, GenericParamKind, HirId}; +use rustc_hir::{Body, Constness, FnDecl, GenericParamKind}; use rustc_hir_analysis::hir_ty_to_ty; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::def_id::LocalDefId; use rustc_span::Span; declare_clippy_lint! { @@ -91,14 +92,12 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { _: &FnDecl<'_>, body: &Body<'tcx>, span: Span, - hir_id: HirId, + def_id: LocalDefId, ) { if !self.msrv.meets(msrvs::CONST_IF_MATCH) { return; } - let def_id = cx.tcx.hir().local_def_id(hir_id); - if in_external_macro(cx.tcx.sess, span) || is_entrypoint_fn(cx, def_id.to_def_id()) { return; } @@ -132,6 +131,8 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { FnKind::Closure => return, } + let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id); + // Const fns are not allowed as methods in a trait. { let parent = cx.tcx.hir().get_parent_item(hir_id).def_id; diff --git a/src/tools/clippy/clippy_lints/src/missing_doc.rs b/src/tools/clippy/clippy_lints/src/missing_doc.rs index 6fd100762..9659ca8ce 100644 --- a/src/tools/clippy/clippy_lints/src/missing_doc.rs +++ b/src/tools/clippy/clippy_lints/src/missing_doc.rs @@ -8,10 +8,12 @@ use clippy_utils::attrs::is_doc_hidden; use clippy_utils::diagnostics::span_lint; use clippy_utils::is_from_proc_macro; +use hir::def_id::LocalDefId; +use if_chain::if_chain; use rustc_ast::ast::{self, MetaItem, MetaItemKind}; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty::DefIdTree; +use rustc_middle::ty::{DefIdTree, Visibility}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::def_id::CRATE_DEF_ID; use rustc_span::source_map::Span; @@ -34,6 +36,9 @@ declare_clippy_lint! { } pub struct MissingDoc { + /// Whether to **only** check for missing documentation in items visible within the current + /// crate. For example, `pub(crate)` items. + crate_items_only: bool, /// Stack of whether #[doc(hidden)] is set /// at each level which has lint attributes. doc_hidden_stack: Vec<bool>, @@ -42,14 +47,15 @@ pub struct MissingDoc { impl Default for MissingDoc { #[must_use] fn default() -> Self { - Self::new() + Self::new(false) } } impl MissingDoc { #[must_use] - pub fn new() -> Self { + pub fn new(crate_items_only: bool) -> Self { Self { + crate_items_only, doc_hidden_stack: vec![false], } } @@ -75,6 +81,7 @@ impl MissingDoc { fn check_missing_docs_attrs( &self, cx: &LateContext<'_>, + def_id: LocalDefId, attrs: &[ast::Attribute], sp: Span, article: &'static str, @@ -95,6 +102,13 @@ impl MissingDoc { return; } + if self.crate_items_only && def_id != CRATE_DEF_ID { + let vis = cx.tcx.visibility(def_id); + if vis == Visibility::Public || vis != Visibility::Restricted(CRATE_DEF_ID.into()) { + return; + } + } + let has_doc = attrs .iter() .any(|a| a.doc_str().is_some() || Self::has_include(a.meta())); @@ -123,7 +137,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { fn check_crate(&mut self, cx: &LateContext<'tcx>) { let attrs = cx.tcx.hir().attrs(hir::CRATE_HIR_ID); - self.check_missing_docs_attrs(cx, attrs, cx.tcx.def_span(CRATE_DEF_ID), "the", "crate"); + self.check_missing_docs_attrs(cx, CRATE_DEF_ID, attrs, cx.tcx.def_span(CRATE_DEF_ID), "the", "crate"); } fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) { @@ -159,7 +173,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { let attrs = cx.tcx.hir().attrs(it.hir_id()); if !is_from_proc_macro(cx, it) { - self.check_missing_docs_attrs(cx, attrs, it.span, article, desc); + self.check_missing_docs_attrs(cx, it.owner_id.def_id, attrs, it.span, article, desc); } } @@ -168,7 +182,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { let attrs = cx.tcx.hir().attrs(trait_item.hir_id()); if !is_from_proc_macro(cx, trait_item) { - self.check_missing_docs_attrs(cx, attrs, trait_item.span, article, desc); + self.check_missing_docs_attrs(cx, trait_item.owner_id.def_id, attrs, trait_item.span, article, desc); } } @@ -185,7 +199,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { let (article, desc) = cx.tcx.article_and_description(impl_item.owner_id.to_def_id()); let attrs = cx.tcx.hir().attrs(impl_item.hir_id()); if !is_from_proc_macro(cx, impl_item) { - self.check_missing_docs_attrs(cx, attrs, impl_item.span, article, desc); + self.check_missing_docs_attrs(cx, impl_item.owner_id.def_id, attrs, impl_item.span, article, desc); } } @@ -193,7 +207,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { if !sf.is_positional() { let attrs = cx.tcx.hir().attrs(sf.hir_id); if !is_from_proc_macro(cx, sf) { - self.check_missing_docs_attrs(cx, attrs, sf.span, "a", "struct field"); + self.check_missing_docs_attrs(cx, sf.def_id, attrs, sf.span, "a", "struct field"); } } } @@ -201,7 +215,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { fn check_variant(&mut self, cx: &LateContext<'tcx>, v: &'tcx hir::Variant<'_>) { let attrs = cx.tcx.hir().attrs(v.hir_id); if !is_from_proc_macro(cx, v) { - self.check_missing_docs_attrs(cx, attrs, v.span, "a", "variant"); + self.check_missing_docs_attrs(cx, v.def_id, attrs, v.span, "a", "variant"); } } } diff --git a/src/tools/clippy/clippy_lints/src/missing_trait_methods.rs b/src/tools/clippy/clippy_lints/src/missing_trait_methods.rs index 3371b4cce..e99081ad0 100644 --- a/src/tools/clippy/clippy_lints/src/missing_trait_methods.rs +++ b/src/tools/clippy/clippy_lints/src/missing_trait_methods.rs @@ -94,7 +94,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingTraitMethods { "implement the method", ); } - }) + }); } } } diff --git a/src/tools/clippy/clippy_lints/src/module_style.rs b/src/tools/clippy/clippy_lints/src/module_style.rs index 0742943df..349fcd227 100644 --- a/src/tools/clippy/clippy_lints/src/module_style.rs +++ b/src/tools/clippy/clippy_lints/src/module_style.rs @@ -134,7 +134,7 @@ fn process_paths_for_mod_files<'a>( mod_folders: &mut FxHashSet<&'a OsStr>, ) { let mut comp = path.components().rev().peekable(); - let _ = comp.next(); + let _: Option<_> = comp.next(); if path.ends_with("mod.rs") { mod_folders.insert(comp.peek().map(|c| c.as_os_str()).unwrap_or_default()); } diff --git a/src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs b/src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs new file mode 100644 index 000000000..63c575fca --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs @@ -0,0 +1,186 @@ +use clippy_utils::{ + diagnostics::span_lint_and_then, + visitors::{for_each_expr_with_closures, Descend, Visitable}, +}; +use core::ops::ControlFlow::Continue; +use hir::{ + def::{DefKind, Res}, + BlockCheckMode, ExprKind, QPath, UnOp, Unsafety, +}; +use rustc_ast::Mutability; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// Checks for `unsafe` blocks that contain more than one unsafe operation. + /// + /// ### Why is this bad? + /// Combined with `undocumented_unsafe_blocks`, + /// this lint ensures that each unsafe operation must be independently justified. + /// Combined with `unused_unsafe`, this lint also ensures + /// elimination of unnecessary unsafe blocks through refactoring. + /// + /// ### Example + /// ```rust + /// /// Reads a `char` from the given pointer. + /// /// + /// /// # Safety + /// /// + /// /// `ptr` must point to four consecutive, initialized bytes which + /// /// form a valid `char` when interpreted in the native byte order. + /// fn read_char(ptr: *const u8) -> char { + /// // SAFETY: The caller has guaranteed that the value pointed + /// // to by `bytes` is a valid `char`. + /// unsafe { char::from_u32_unchecked(*ptr.cast::<u32>()) } + /// } + /// ``` + /// Use instead: + /// ```rust + /// /// Reads a `char` from the given pointer. + /// /// + /// /// # Safety + /// /// + /// /// - `ptr` must be 4-byte aligned, point to four consecutive + /// /// initialized bytes, and be valid for reads of 4 bytes. + /// /// - The bytes pointed to by `ptr` must represent a valid + /// /// `char` when interpreted in the native byte order. + /// fn read_char(ptr: *const u8) -> char { + /// // SAFETY: `ptr` is 4-byte aligned, points to four consecutive + /// // initialized bytes, and is valid for reads of 4 bytes. + /// let int_value = unsafe { *ptr.cast::<u32>() }; + /// + /// // SAFETY: The caller has guaranteed that the four bytes + /// // pointed to by `bytes` represent a valid `char`. + /// unsafe { char::from_u32_unchecked(int_value) } + /// } + /// ``` + #[clippy::version = "1.68.0"] + pub MULTIPLE_UNSAFE_OPS_PER_BLOCK, + restriction, + "more than one unsafe operation per `unsafe` block" +} +declare_lint_pass!(MultipleUnsafeOpsPerBlock => [MULTIPLE_UNSAFE_OPS_PER_BLOCK]); + +impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock { + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { + if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) || in_external_macro(cx.tcx.sess, block.span) { + return; + } + let mut unsafe_ops = vec![]; + collect_unsafe_exprs(cx, block, &mut unsafe_ops); + if unsafe_ops.len() > 1 { + span_lint_and_then( + cx, + MULTIPLE_UNSAFE_OPS_PER_BLOCK, + block.span, + &format!( + "this `unsafe` block contains {} unsafe operations, expected only one", + unsafe_ops.len() + ), + |diag| { + for (msg, span) in unsafe_ops { + diag.span_note(span, msg); + } + }, + ); + } + } +} + +fn collect_unsafe_exprs<'tcx>( + cx: &LateContext<'tcx>, + node: impl Visitable<'tcx>, + unsafe_ops: &mut Vec<(&'static str, Span)>, +) { + for_each_expr_with_closures(cx, node, |expr| { + match expr.kind { + ExprKind::InlineAsm(_) => unsafe_ops.push(("inline assembly used here", expr.span)), + + ExprKind::Field(e, _) => { + if cx.typeck_results().expr_ty(e).is_union() { + unsafe_ops.push(("union field access occurs here", expr.span)); + } + }, + + ExprKind::Path(QPath::Resolved( + _, + hir::Path { + res: Res::Def(DefKind::Static(Mutability::Mut), _), + .. + }, + )) => { + unsafe_ops.push(("access of a mutable static occurs here", expr.span)); + }, + + ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty_adjusted(e).is_unsafe_ptr() => { + unsafe_ops.push(("raw pointer dereference occurs here", expr.span)); + }, + + ExprKind::Call(path_expr, _) => match path_expr.kind { + ExprKind::Path(QPath::Resolved( + _, + hir::Path { + res: Res::Def(kind, def_id), + .. + }, + )) if kind.is_fn_like() => { + let sig = cx.tcx.fn_sig(*def_id); + if sig.0.unsafety() == Unsafety::Unsafe { + unsafe_ops.push(("unsafe function call occurs here", expr.span)); + } + }, + + ExprKind::Path(QPath::TypeRelative(..)) => { + if let Some(sig) = cx + .typeck_results() + .type_dependent_def_id(path_expr.hir_id) + .map(|def_id| cx.tcx.fn_sig(def_id)) + { + if sig.0.unsafety() == Unsafety::Unsafe { + unsafe_ops.push(("unsafe function call occurs here", expr.span)); + } + } + }, + + _ => {}, + }, + + ExprKind::MethodCall(..) => { + if let Some(sig) = cx + .typeck_results() + .type_dependent_def_id(expr.hir_id) + .map(|def_id| cx.tcx.fn_sig(def_id)) + { + if sig.0.unsafety() == Unsafety::Unsafe { + unsafe_ops.push(("unsafe method call occurs here", expr.span)); + } + } + }, + + ExprKind::AssignOp(_, lhs, rhs) | ExprKind::Assign(lhs, rhs, _) => { + if matches!( + lhs.kind, + ExprKind::Path(QPath::Resolved( + _, + hir::Path { + res: Res::Def(DefKind::Static(Mutability::Mut), _), + .. + } + )) + ) { + unsafe_ops.push(("modification of a mutable static occurs here", expr.span)); + collect_unsafe_exprs(cx, rhs, unsafe_ops); + return Continue(Descend::No); + } + }, + + _ => {}, + }; + + Continue::<(), _>(Descend::Yes) + }); +} diff --git a/src/tools/clippy/clippy_lints/src/mut_key.rs b/src/tools/clippy/clippy_lints/src/mut_key.rs index a651020ca..8aa814b74 100644 --- a/src/tools/clippy/clippy_lints/src/mut_key.rs +++ b/src/tools/clippy/clippy_lints/src/mut_key.rs @@ -3,9 +3,10 @@ use clippy_utils::{def_path_def_ids, trait_ref_of_method}; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::TypeVisitable; +use rustc_middle::ty::TypeVisitableExt; use rustc_middle::ty::{Adt, Array, Ref, Slice, Tuple, Ty}; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Span; use rustc_span::symbol::sym; use std::iter; @@ -102,21 +103,21 @@ impl<'tcx> LateLintPass<'tcx> for MutableKeyType { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { if let hir::ItemKind::Fn(ref sig, ..) = item.kind { - self.check_sig(cx, item.hir_id(), sig.decl); + self.check_sig(cx, item.owner_id.def_id, sig.decl); } } fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'tcx>) { if let hir::ImplItemKind::Fn(ref sig, ..) = item.kind { if trait_ref_of_method(cx, item.owner_id.def_id).is_none() { - self.check_sig(cx, item.hir_id(), sig.decl); + self.check_sig(cx, item.owner_id.def_id, sig.decl); } } } fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'tcx>) { if let hir::TraitItemKind::Fn(ref sig, ..) = item.kind { - self.check_sig(cx, item.hir_id(), sig.decl); + self.check_sig(cx, item.owner_id.def_id, sig.decl); } } @@ -136,9 +137,8 @@ impl MutableKeyType { } } - fn check_sig(&self, cx: &LateContext<'_>, item_hir_id: hir::HirId, decl: &hir::FnDecl<'_>) { - let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id); - let fn_sig = cx.tcx.fn_sig(fn_def_id); + fn check_sig(&self, cx: &LateContext<'_>, fn_def_id: LocalDefId, decl: &hir::FnDecl<'_>) { + let fn_sig = cx.tcx.fn_sig(fn_def_id).subst_identity(); for (hir_ty, ty) in iter::zip(decl.inputs, fn_sig.inputs().skip_binder()) { self.check_ty_(cx, hir_ty.span, *ty); } @@ -166,7 +166,8 @@ impl MutableKeyType { Ref(_, inner_ty, mutbl) => mutbl == hir::Mutability::Mut || self.is_interior_mutable_type(cx, inner_ty), Slice(inner_ty) => self.is_interior_mutable_type(cx, inner_ty), Array(inner_ty, size) => { - size.try_eval_usize(cx.tcx, cx.param_env).map_or(true, |u| u != 0) + size.try_eval_target_usize(cx.tcx, cx.param_env) + .map_or(true, |u| u != 0) && self.is_interior_mutable_type(cx, inner_ty) }, Tuple(fields) => fields.iter().any(|ty| self.is_interior_mutable_type(cx, ty)), diff --git a/src/tools/clippy/clippy_lints/src/mut_reference.rs b/src/tools/clippy/clippy_lints/src/mut_reference.rs index 4547ed7ea..e91aac41b 100644 --- a/src/tools/clippy/clippy_lints/src/mut_reference.rs +++ b/src/tools/clippy/clippy_lints/src/mut_reference.rs @@ -52,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMutPassed { ExprKind::MethodCall(path, receiver, arguments, _) => { let def_id = cx.typeck_results().type_dependent_def_id(e.hir_id).unwrap(); let substs = cx.typeck_results().node_substs(e.hir_id); - let method_type = cx.tcx.bound_type_of(def_id).subst(cx.tcx, substs); + let method_type = cx.tcx.type_of(def_id).subst(cx.tcx, substs); check_arguments( cx, std::iter::once(receiver).chain(arguments.iter()).collect(), diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs index 8c9d4c5cf..1ab81aee7 100644 --- a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs +++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs @@ -18,8 +18,9 @@ use rustc_hir_typeck::expr_use_visitor as euv; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::FakeReadCause; -use rustc_middle::ty::{self, TypeVisitable}; +use rustc_middle::ty::{self, TypeVisitableExt}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::def_id::LocalDefId; use rustc_span::symbol::kw; use rustc_span::{sym, Span}; use rustc_target::spec::abi::Abi; @@ -82,12 +83,14 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { decl: &'tcx FnDecl<'_>, body: &'tcx Body<'_>, span: Span, - hir_id: HirId, + fn_def_id: LocalDefId, ) { if span.from_expansion() { return; } + let hir_id = cx.tcx.hir().local_def_id_to_hir_id(fn_def_id); + match kind { FnKind::ItemFn(.., header) => { let attrs = cx.tcx.hir().attrs(hir_id); @@ -119,8 +122,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { let sized_trait = need!(cx.tcx.lang_items().sized_trait()); - let fn_def_id = cx.tcx.hir().local_def_id(hir_id); - let preds = traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds().iter()) .filter(|p| !p.is_global()) .filter_map(|obligation| { @@ -147,8 +148,8 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { ctx }; - let fn_sig = cx.tcx.fn_sig(fn_def_id); - let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig); + let fn_sig = cx.tcx.fn_sig(fn_def_id).subst_identity(); + let fn_sig = cx.tcx.liberate_late_bound_regions(fn_def_id.to_def_id(), fn_sig); for (idx, ((input, &ty), arg)) in decl.inputs.iter().zip(fn_sig.inputs()).zip(body.params).enumerate() { // All spans generated from a proc-macro invocation are the same... diff --git a/src/tools/clippy/clippy_lints/src/new_without_default.rs b/src/tools/clippy/clippy_lints/src/new_without_default.rs index 54a3c82b7..653b1a8a0 100644 --- a/src/tools/clippy/clippy_lints/src/new_without_default.rs +++ b/src/tools/clippy/clippy_lints/src/new_without_default.rs @@ -75,7 +75,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { } if let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind { let name = impl_item.ident.name; - let id = impl_item.hir_id(); + let id = impl_item.owner_id; if sig.header.constness == hir::Constness::Const { // can't be implemented by default return; @@ -97,15 +97,16 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { if sig.decl.inputs.is_empty(); if name == sym::new; if cx.effective_visibilities.is_reachable(impl_item.owner_id.def_id); - let self_def_id = cx.tcx.hir().get_parent_item(id); - let self_ty = cx.tcx.type_of(self_def_id); + let self_def_id = cx.tcx.hir().get_parent_item(id.into()); + let self_ty = cx.tcx.type_of(self_def_id).subst_identity(); if self_ty == return_ty(cx, id); if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default); then { if self.impling_types.is_none() { let mut impls = HirIdSet::default(); cx.tcx.for_each_impl(default_trait_id, |d| { - if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() { + let ty = cx.tcx.type_of(d).subst_identity(); + if let Some(ty_def) = ty.ty_adt_def() { if let Some(local_def_id) = ty_def.did().as_local() { impls.insert(cx.tcx.hir().local_def_id_to_hir_id(local_def_id)); } @@ -118,7 +119,8 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { // generics if_chain! { if let Some(ref impling_types) = self.impling_types; - if let Some(self_def) = cx.tcx.type_of(self_def_id).ty_adt_def(); + let self_def = cx.tcx.type_of(self_def_id).subst_identity(); + if let Some(self_def) = self_def.ty_adt_def(); if let Some(self_local_did) = self_def.did().as_local(); let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_local_did); if impling_types.contains(&self_id); @@ -133,7 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { span_lint_hir_and_then( cx, NEW_WITHOUT_DEFAULT, - id, + id.into(), impl_item.span, &format!( "you should consider adding a `Default` implementation for `{self_type_snip}`" diff --git a/src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs b/src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs new file mode 100644 index 000000000..bc64ccb29 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs @@ -0,0 +1,65 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use rustc_errors::Applicability; +use rustc_hir::{Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_target::spec::abi::Abi; + +declare_clippy_lint! { + /// ### What it does + /// Checks for Rust ABI functions with the `#[no_mangle]` attribute. + /// + /// ### Why is this bad? + /// The Rust ABI is not stable, but in many simple cases matches + /// enough with the C ABI that it is possible to forget to add + /// `extern "C"` to a function called from C. Changes to the + /// Rust ABI can break this at any point. + /// + /// ### Example + /// ```rust + /// #[no_mangle] + /// fn example(arg_one: u32, arg_two: usize) {} + /// ``` + /// + /// Use instead: + /// ```rust + /// #[no_mangle] + /// extern "C" fn example(arg_one: u32, arg_two: usize) {} + /// ``` + #[clippy::version = "1.69.0"] + pub NO_MANGLE_WITH_RUST_ABI, + pedantic, + "convert Rust ABI functions to C ABI" +} +declare_lint_pass!(NoMangleWithRustAbi => [NO_MANGLE_WITH_RUST_ABI]); + +impl<'tcx> LateLintPass<'tcx> for NoMangleWithRustAbi { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if let ItemKind::Fn(fn_sig, _, _) = &item.kind { + let attrs = cx.tcx.hir().attrs(item.hir_id()); + let mut applicability = Applicability::MachineApplicable; + let snippet = snippet_with_applicability(cx, fn_sig.span, "..", &mut applicability); + for attr in attrs { + if let Some(ident) = attr.ident() + && ident.name == rustc_span::sym::no_mangle + && fn_sig.header.abi == Abi::Rust + && !snippet.contains("extern") { + + let suggestion = snippet.split_once("fn") + .map_or(String::new(), |(first, second)| format!(r#"{first}extern "C" fn{second}"#)); + + span_lint_and_sugg( + cx, + NO_MANGLE_WITH_RUST_ABI, + fn_sig.span, + "attribute #[no_mangle] set on a Rust ABI function", + "try", + suggestion, + applicability + ); + } + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/non_copy_const.rs b/src/tools/clippy/clippy_lints/src/non_copy_const.rs index 07fd321d6..0bedab05e 100644 --- a/src/tools/clippy/clippy_lints/src/non_copy_const.rs +++ b/src/tools/clippy/clippy_lints/src/non_copy_const.rs @@ -313,7 +313,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst { // and, in that case, the definition is *not* generic. cx.tcx.normalize_erasing_regions( cx.tcx.param_env(of_trait_def_id), - cx.tcx.type_of(of_assoc_item), + cx.tcx.type_of(of_assoc_item).subst_identity(), ), )) .is_err(); diff --git a/src/tools/clippy/clippy_lints/src/only_used_in_recursion.rs b/src/tools/clippy/clippy_lints/src/only_used_in_recursion.rs index 7b1d974f2..8b77a5c99 100644 --- a/src/tools/clippy/clippy_lints/src/only_used_in_recursion.rs +++ b/src/tools/clippy/clippy_lints/src/only_used_in_recursion.rs @@ -7,7 +7,7 @@ use rustc_hir::def_id::DefId; use rustc_hir::hir_id::HirIdMap; use rustc_hir::{Body, Expr, ExprKind, HirId, ImplItem, ImplItemKind, Node, PatKind, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::subst::{GenericArgKind, SubstsRef}; +use rustc_middle::ty::subst::{EarlyBinder, GenericArgKind, SubstsRef}; use rustc_middle::ty::{self, ConstKind}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::symbol::{kw, Ident}; @@ -244,7 +244,7 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion { })) => { #[allow(trivial_casts)] if let Some(Node::Item(item)) = get_parent_node(cx.tcx, owner_id.into()) - && let Some(trait_ref) = cx.tcx.impl_trait_ref(item.owner_id).map(|t| t.subst_identity()) + && let Some(trait_ref) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::subst_identity) && let Some(trait_item_id) = cx.tcx.associated_item(owner_id).trait_item_def_id { ( diff --git a/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs b/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs index cff82b875..87a8a2ed1 100644 --- a/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -1,8 +1,8 @@ use super::ARITHMETIC_SIDE_EFFECTS; use clippy_utils::{ - consts::{constant, constant_simple}, + consts::{constant, constant_simple, Constant}, diagnostics::span_lint, - peel_hir_expr_refs, peel_hir_expr_unary, + is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary, }; use rustc_ast as ast; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; @@ -97,17 +97,19 @@ impl ArithmeticSideEffects { self.expr_span = Some(expr.span); } - /// If `expr` is not a literal integer like `1`, returns `None`. + /// Returns the numeric value of a literal integer originated from `expr`, if any. /// - /// Returns the absolute value of the expression, if this is an integer literal. - fn literal_integer(expr: &hir::Expr<'_>) -> Option<u128> { + /// Literal integers can be originated from adhoc declarations like `1`, associated constants + /// like `i32::MAX` or constant references like `N` from `const N: i32 = 1;`, + fn literal_integer(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<u128> { let actual = peel_hir_expr_unary(expr).0; if let hir::ExprKind::Lit(ref lit) = actual.kind && let ast::LitKind::Int(n, _) = lit.node { - Some(n) + return Some(n) } - else { - None + if let Some((Constant::Int(n), _)) = constant(cx, cx.typeck_results(), expr) { + return Some(n); } + None } /// Manages when the lint should be triggered. Operations in constant environments, hard coded @@ -143,7 +145,10 @@ impl ArithmeticSideEffects { let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) { let (actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs); let (actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs); - match (Self::literal_integer(actual_lhs), Self::literal_integer(actual_rhs)) { + match ( + Self::literal_integer(cx, actual_lhs), + Self::literal_integer(cx, actual_rhs), + ) { (None, None) => false, (None, Some(n)) | (Some(n), None) => match (&op.node, n) { (hir::BinOpKind::Div | hir::BinOpKind::Rem, 0) => false, @@ -180,20 +185,22 @@ impl ArithmeticSideEffects { return; } let actual_un_expr = peel_hir_expr_refs(un_expr).0; - if Self::literal_integer(actual_un_expr).is_some() { + if Self::literal_integer(cx, actual_un_expr).is_some() { return; } self.issue_lint(cx, expr); } - fn should_skip_expr(&mut self, expr: &hir::Expr<'_>) -> bool { - self.expr_span.is_some() || self.const_span.map_or(false, |sp| sp.contains(expr.span)) + fn should_skip_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + is_lint_allowed(cx, ARITHMETIC_SIDE_EFFECTS, expr.hir_id) + || self.expr_span.is_some() + || self.const_span.map_or(false, |sp| sp.contains(expr.span)) } } impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) { - if self.should_skip_expr(expr) { + if self.should_skip_expr(cx, expr) { return; } match &expr.kind { @@ -209,7 +216,8 @@ impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects { fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) { let body_owner = cx.tcx.hir().body_owner(body.id()); - let body_owner_def_id = cx.tcx.hir().local_def_id(body_owner); + let body_owner_def_id = cx.tcx.hir().body_owner_def_id(body.id()); + let body_owner_kind = cx.tcx.hir().body_owner_kind(body_owner_def_id); if let hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) = body_owner_kind { let body_span = cx.tcx.hir().span_with_body(body_owner); diff --git a/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs b/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs index 24aeb82a3..d3de9699f 100644 --- a/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs +++ b/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs @@ -49,10 +49,10 @@ fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) (arg, arg.span) }, ExprKind::Call(path, [arg]) - if path_def_id(cx, path).map_or(false, |id| { - if match_def_path(cx, id, &paths::FROM_STR_METHOD) { + if path_def_id(cx, path).map_or(false, |did| { + if match_def_path(cx, did, &paths::FROM_STR_METHOD) { true - } else if cx.tcx.lang_items().from_fn() == Some(id) { + } else if cx.tcx.is_diagnostic_item(sym::from_fn, did) { !is_copy(cx, typeck.expr_ty(expr)) } else { false diff --git a/src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs b/src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs index 0830a106f..777395f45 100644 --- a/src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs +++ b/src/tools/clippy/clippy_lints/src/operators/numeric_arithmetic.rs @@ -96,7 +96,7 @@ impl Context { pub fn enter_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) { let body_owner = cx.tcx.hir().body_owner(body.id()); - let body_owner_def_id = cx.tcx.hir().local_def_id(body_owner); + let body_owner_def_id = cx.tcx.hir().body_owner_def_id(body.id()); match cx.tcx.hir().body_owner_kind(body_owner_def_id) { hir::BodyOwnerKind::Static(_) | hir::BodyOwnerKind::Const => { diff --git a/src/tools/clippy/clippy_lints/src/option_if_let_else.rs b/src/tools/clippy/clippy_lints/src/option_if_let_else.rs index 472f52380..c5ea09590 100644 --- a/src/tools/clippy/clippy_lints/src/option_if_let_else.rs +++ b/src/tools/clippy/clippy_lints/src/option_if_let_else.rs @@ -25,11 +25,11 @@ declare_clippy_lint! { /// Using the dedicated functions of the `Option` type is clearer and /// more concise than an `if let` expression. /// - /// ### Known problems - /// This lint uses a deliberately conservative metric for checking - /// if the inside of either body contains breaks or continues which will - /// cause it to not suggest a fix if either block contains a loop with - /// continues or breaks contained within the loop. + /// ### Notes + /// This lint uses a deliberately conservative metric for checking if the + /// inside of either body contains loop control expressions `break` or + /// `continue` (which cannot be used within closures). If these are found, + /// this lint will not be raised. /// /// ### Example /// ```rust diff --git a/src/tools/clippy/clippy_lints/src/panic_in_result_fn.rs b/src/tools/clippy/clippy_lints/src/panic_in_result_fn.rs index efec12489..849cd03dd 100644 --- a/src/tools/clippy/clippy_lints/src/panic_in_result_fn.rs +++ b/src/tools/clippy/clippy_lints/src/panic_in_result_fn.rs @@ -8,6 +8,7 @@ use rustc_hir as hir; use rustc_hir::intravisit::FnKind; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::def_id::LocalDefId; use rustc_span::{sym, Span}; declare_clippy_lint! { @@ -49,9 +50,13 @@ impl<'tcx> LateLintPass<'tcx> for PanicInResultFn { _: &'tcx hir::FnDecl<'tcx>, body: &'tcx hir::Body<'tcx>, span: Span, - hir_id: hir::HirId, + def_id: LocalDefId, ) { - if !matches!(fn_kind, FnKind::Closure) && is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::Result) { + if matches!(fn_kind, FnKind::Closure) { + return; + } + let owner = cx.tcx.hir().local_def_id_to_hir_id(def_id).expect_owner(); + if is_type_diagnostic_item(cx, return_ty(cx, owner), sym::Result) { lint_impl_body(cx, span, body); } } diff --git a/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs b/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs index 2d21aaa4f..0d78c3048 100644 --- a/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs +++ b/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs @@ -12,7 +12,7 @@ use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::intravisit::FnKind; -use rustc_hir::{BindingAnnotation, Body, FnDecl, HirId, Impl, ItemKind, MutTy, Mutability, Node, PatKind}; +use rustc_hir::{BindingAnnotation, Body, FnDecl, Impl, ItemKind, MutTy, Mutability, Node, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, PointerCast}; use rustc_middle::ty::layout::LayoutOf; @@ -143,7 +143,7 @@ impl<'tcx> PassByRefOrValue { return; } - let fn_sig = cx.tcx.fn_sig(def_id); + let fn_sig = cx.tcx.fn_sig(def_id).subst_identity(); let fn_body = cx.enclosing_body.map(|id| cx.tcx.hir().body(id)); // Gather all the lifetimes found in the output type which may affect whether @@ -272,12 +272,13 @@ impl<'tcx> LateLintPass<'tcx> for PassByRefOrValue { decl: &'tcx FnDecl<'_>, _body: &'tcx Body<'_>, span: Span, - hir_id: HirId, + def_id: LocalDefId, ) { if span.from_expansion() { return; } + let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id); match kind { FnKind::ItemFn(.., header) => { if header.abi != Abi::Rust { @@ -308,6 +309,6 @@ impl<'tcx> LateLintPass<'tcx> for PassByRefOrValue { } } - self.check_poly_fn(cx, cx.tcx.hir().local_def_id(hir_id), decl, Some(span)); + self.check_poly_fn(cx, def_id, decl, Some(span)); } } diff --git a/src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs b/src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs index 97b5a4ce3..9f98195d3 100644 --- a/src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs +++ b/src/tools/clippy/clippy_lints/src/pattern_type_mismatch.rs @@ -1,11 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_help; -use rustc_hir::{ - intravisit, Body, Expr, ExprKind, FnDecl, HirId, Let, LocalSource, Mutability, Pat, PatKind, Stmt, StmtKind, -}; +use rustc_hir::{intravisit, Body, Expr, ExprKind, FnDecl, Let, LocalSource, Mutability, Pat, PatKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Span; declare_clippy_lint! { @@ -116,7 +115,7 @@ impl<'tcx> LateLintPass<'tcx> for PatternTypeMismatch { _: &'tcx FnDecl<'_>, body: &'tcx Body<'_>, _: Span, - _: HirId, + _: LocalDefId, ) { for param in body.params { apply_lint(cx, param.pat, DerefPossible::Impossible); diff --git a/src/tools/clippy/clippy_lints/src/ptr.rs b/src/tools/clippy/clippy_lints/src/ptr.rs index 262953042..fc5509361 100644 --- a/src/tools/clippy/clippy_lints/src/ptr.rs +++ b/src/tools/clippy/clippy_lints/src/ptr.rs @@ -164,7 +164,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { check_mut_from_ref(cx, sig, None); for arg in check_fn_args( cx, - cx.tcx.fn_sig(item.owner_id).skip_binder().inputs(), + cx.tcx.fn_sig(item.owner_id).subst_identity().skip_binder().inputs(), sig.decl.inputs, &[], ) @@ -217,7 +217,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { check_mut_from_ref(cx, sig, Some(body)); let decl = sig.decl; - let sig = cx.tcx.fn_sig(item_id).skip_binder(); + let sig = cx.tcx.fn_sig(item_id).subst_identity().skip_binder(); let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params) .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not) .collect(); @@ -505,13 +505,13 @@ fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Optio if let FnRetTy::Return(ty) = sig.decl.output && let Some((out, Mutability::Mut, _)) = get_ref_lm(ty) { - let out_region = cx.tcx.named_region(out.hir_id); + let out_region = cx.tcx.named_bound_var(out.hir_id); let args: Option<Vec<_>> = sig .decl .inputs .iter() .filter_map(get_ref_lm) - .filter(|&(lt, _, _)| cx.tcx.named_region(lt.hir_id) == out_region) + .filter(|&(lt, _, _)| cx.tcx.named_bound_var(lt.hir_id) == out_region) .map(|(_, mutability, span)| (mutability == Mutability::Not).then_some(span)) .collect(); if let Some(args) = args @@ -624,7 +624,10 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: return; }; - match *self.cx.tcx.fn_sig(id).skip_binder().inputs()[i].peel_refs().kind() { + match *self.cx.tcx.fn_sig(id).subst_identity().skip_binder().inputs()[i] + .peel_refs() + .kind() + { ty::Dynamic(preds, _, _) if !matches_preds(self.cx, args.deref_ty.ty(self.cx), preds) => { set_skip_flag(); }, diff --git a/src/tools/clippy/clippy_lints/src/question_mark_used.rs b/src/tools/clippy/clippy_lints/src/question_mark_used.rs new file mode 100644 index 000000000..9b678e8d7 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/question_mark_used.rs @@ -0,0 +1,52 @@ +use clippy_utils::diagnostics::span_lint_and_help; + +use clippy_utils::macros::span_is_local; +use rustc_hir::{Expr, ExprKind, MatchSource}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for expressions that use the question mark operator and rejects them. + /// + /// ### Why is this bad? + /// Sometimes code wants to avoid the question mark operator because for instance a local + /// block requires a macro to re-throw errors to attach additional information to the + /// error. + /// + /// ### Example + /// ```ignore + /// let result = expr?; + /// ``` + /// + /// Could be written: + /// + /// ```ignore + /// utility_macro!(expr); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub QUESTION_MARK_USED, + restriction, + "complains if the question mark operator is used" +} + +declare_lint_pass!(QuestionMarkUsed => [QUESTION_MARK_USED]); + +impl<'tcx> LateLintPass<'tcx> for QuestionMarkUsed { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Match(_, _, MatchSource::TryDesugar) = expr.kind { + if !span_is_local(expr.span) { + return; + } + + span_lint_and_help( + cx, + QUESTION_MARK_USED, + expr.span, + "question mark operator was used", + None, + "consider using a custom macro or match expression", + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/redundant_clone.rs b/src/tools/clippy/clippy_lints/src/redundant_clone.rs index c1677fb3d..944a33cc3 100644 --- a/src/tools/clippy/clippy_lints/src/redundant_clone.rs +++ b/src/tools/clippy/clippy_lints/src/redundant_clone.rs @@ -6,11 +6,12 @@ use clippy_utils::{fn_has_unsatisfiable_preds, match_def_path, paths}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; -use rustc_hir::{def_id, Body, FnDecl, HirId, LangItem}; +use rustc_hir::{def_id, Body, FnDecl, LangItem}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir; use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::def_id::LocalDefId; use rustc_span::source_map::{BytePos, Span}; use rustc_span::sym; @@ -69,12 +70,10 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone { cx: &LateContext<'tcx>, _: FnKind<'tcx>, _: &'tcx FnDecl<'_>, - body: &'tcx Body<'_>, + _: &'tcx Body<'_>, _: Span, - _: HirId, + def_id: LocalDefId, ) { - let def_id = cx.tcx.hir().body_owner_def_id(body.id()); - // Building MIR for `fn`s with unsatisfiable preds results in ICE. if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) { return; diff --git a/src/tools/clippy/clippy_lints/src/redundant_slicing.rs b/src/tools/clippy/clippy_lints/src/redundant_slicing.rs index 245a02ea2..2fdd775ad 100644 --- a/src/tools/clippy/clippy_lints/src/redundant_slicing.rs +++ b/src/tools/clippy/clippy_lints/src/redundant_slicing.rs @@ -11,8 +11,6 @@ use rustc_middle::ty::adjustment::{Adjust, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::subst::GenericArg; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use std::iter; - declare_clippy_lint! { /// ### What it does /// Checks for redundant slicing expressions which use the full range, and @@ -136,7 +134,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { } else if let Some(target_id) = cx.tcx.lang_items().deref_target() { if let Ok(deref_ty) = cx.tcx.try_normalize_erasing_regions( cx.param_env, - cx.tcx.mk_projection(target_id, cx.tcx.mk_substs(iter::once(GenericArg::from(indexed_ty)))), + cx.tcx.mk_projection(target_id, cx.tcx.mk_substs(&[GenericArg::from(indexed_ty)])), ) { if deref_ty == expr_ty { let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0; diff --git a/src/tools/clippy/clippy_lints/src/regex.rs b/src/tools/clippy/clippy_lints/src/regex.rs index 1fda58fa5..9e6c6c73d 100644 --- a/src/tools/clippy/clippy_lints/src/regex.rs +++ b/src/tools/clippy/clippy_lints/src/regex.rs @@ -1,5 +1,8 @@ +use std::fmt::Display; + use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; +use clippy_utils::source::snippet_opt; use clippy_utils::{match_def_path, paths}; use if_chain::if_chain; use rustc_ast::ast::{LitKind, StrStyle}; @@ -77,13 +80,45 @@ impl<'tcx> LateLintPass<'tcx> for Regex { } } -#[must_use] -fn str_span(base: Span, c: regex_syntax::ast::Span, offset: u8) -> Span { - let offset = u32::from(offset); - let end = base.lo() + BytePos(u32::try_from(c.end.offset).expect("offset too large") + offset); - let start = base.lo() + BytePos(u32::try_from(c.start.offset).expect("offset too large") + offset); - assert!(start <= end); - Span::new(start, end, base.ctxt(), base.parent()) +fn lint_syntax_error(cx: &LateContext<'_>, error: ®ex_syntax::Error, unescaped: &str, base: Span, offset: u8) { + let parts: Option<(_, _, &dyn Display)> = match &error { + regex_syntax::Error::Parse(e) => Some((e.span(), e.auxiliary_span(), e.kind())), + regex_syntax::Error::Translate(e) => Some((e.span(), None, e.kind())), + _ => None, + }; + + let convert_span = |regex_span: ®ex_syntax::ast::Span| { + let offset = u32::from(offset); + let start = base.lo() + BytePos(u32::try_from(regex_span.start.offset).expect("offset too large") + offset); + let end = base.lo() + BytePos(u32::try_from(regex_span.end.offset).expect("offset too large") + offset); + + Span::new(start, end, base.ctxt(), base.parent()) + }; + + if let Some((primary, auxiliary, kind)) = parts + && let Some(literal_snippet) = snippet_opt(cx, base) + && let Some(inner) = literal_snippet.get(offset as usize..) + // Only convert to native rustc spans if the parsed regex matches the + // source snippet exactly, to ensure the span offsets are correct + && inner.get(..unescaped.len()) == Some(unescaped) + { + let spans = if let Some(auxiliary) = auxiliary { + vec![convert_span(primary), convert_span(auxiliary)] + } else { + vec![convert_span(primary)] + }; + + span_lint(cx, INVALID_REGEX, spans, &format!("regex syntax error: {kind}")); + } else { + span_lint_and_help( + cx, + INVALID_REGEX, + base, + &error.to_string(), + None, + "consider using a raw string literal: `r\"..\"`", + ); + } } fn const_str<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<String> { @@ -155,25 +190,7 @@ fn check_regex<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, utf8: bool) { span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", None, repl); } }, - Err(regex_syntax::Error::Parse(e)) => { - span_lint( - cx, - INVALID_REGEX, - str_span(expr.span, *e.span(), offset), - &format!("regex syntax error: {}", e.kind()), - ); - }, - Err(regex_syntax::Error::Translate(e)) => { - span_lint( - cx, - INVALID_REGEX, - str_span(expr.span, *e.span(), offset), - &format!("regex syntax error: {}", e.kind()), - ); - }, - Err(e) => { - span_lint(cx, INVALID_REGEX, expr.span, &format!("regex syntax error: {e}")); - }, + Err(e) => lint_syntax_error(cx, &e, r, expr.span, offset), } } } else if let Some(r) = const_str(cx, expr) { @@ -183,25 +200,7 @@ fn check_regex<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, utf8: bool) { span_lint_and_help(cx, TRIVIAL_REGEX, expr.span, "trivial regex", None, repl); } }, - Err(regex_syntax::Error::Parse(e)) => { - span_lint( - cx, - INVALID_REGEX, - expr.span, - &format!("regex syntax error on position {}: {}", e.span().start.offset, e.kind()), - ); - }, - Err(regex_syntax::Error::Translate(e)) => { - span_lint( - cx, - INVALID_REGEX, - expr.span, - &format!("regex syntax error on position {}: {}", e.span().start.offset, e.kind()), - ); - }, - Err(e) => { - span_lint(cx, INVALID_REGEX, expr.span, &format!("regex syntax error: {e}")); - }, + Err(e) => span_lint(cx, INVALID_REGEX, expr.span, &e.to_string()), } } } diff --git a/src/tools/clippy/clippy_lints/src/return_self_not_must_use.rs b/src/tools/clippy/clippy_lints/src/return_self_not_must_use.rs index b77faf732..bccf421e8 100644 --- a/src/tools/clippy/clippy_lints/src/return_self_not_must_use.rs +++ b/src/tools/clippy/clippy_lints/src/return_self_not_must_use.rs @@ -3,7 +3,7 @@ use clippy_utils::ty::is_must_use_ty; use clippy_utils::{nth_arg, return_ty}; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, FnDecl, HirId, TraitItem, TraitItemKind}; +use rustc_hir::{Body, FnDecl, OwnerId, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -68,7 +68,7 @@ declare_clippy_lint! { declare_lint_pass!(ReturnSelfNotMustUse => [RETURN_SELF_NOT_MUST_USE]); -fn check_method(cx: &LateContext<'_>, decl: &FnDecl<'_>, fn_def: LocalDefId, span: Span, hir_id: HirId) { +fn check_method(cx: &LateContext<'_>, decl: &FnDecl<'_>, fn_def: LocalDefId, span: Span, owner_id: OwnerId) { if_chain! { // If it comes from an external macro, better ignore it. if !in_external_macro(cx.sess(), span); @@ -76,10 +76,10 @@ fn check_method(cx: &LateContext<'_>, decl: &FnDecl<'_>, fn_def: LocalDefId, spa // We only show this warning for public exported methods. if cx.effective_visibilities.is_exported(fn_def); // We don't want to emit this lint if the `#[must_use]` attribute is already there. - if !cx.tcx.hir().attrs(hir_id).iter().any(|attr| attr.has_name(sym::must_use)); + if !cx.tcx.hir().attrs(owner_id.into()).iter().any(|attr| attr.has_name(sym::must_use)); if cx.tcx.visibility(fn_def.to_def_id()).is_public(); - let ret_ty = return_ty(cx, hir_id); - let self_arg = nth_arg(cx, hir_id, 0); + let ret_ty = return_ty(cx, owner_id); + let self_arg = nth_arg(cx, owner_id, 0); // If `Self` has the same type as the returned type, then we want to warn. // // For this check, we don't want to remove the reference on the returned type because if @@ -109,26 +109,26 @@ impl<'tcx> LateLintPass<'tcx> for ReturnSelfNotMustUse { decl: &'tcx FnDecl<'tcx>, _: &'tcx Body<'tcx>, span: Span, - hir_id: HirId, + fn_def: LocalDefId, ) { if_chain! { // We are only interested in methods, not in functions or associated functions. if matches!(kind, FnKind::Method(_, _)); - if let Some(fn_def) = cx.tcx.hir().opt_local_def_id(hir_id); if let Some(impl_def) = cx.tcx.impl_of_method(fn_def.to_def_id()); // We don't want this method to be te implementation of a trait because the // `#[must_use]` should be put on the trait definition directly. if cx.tcx.trait_id_of_impl(impl_def).is_none(); then { - check_method(cx, decl, fn_def, span, hir_id); + let hir_id = cx.tcx.hir().local_def_id_to_hir_id(fn_def); + check_method(cx, decl, fn_def, span, hir_id.expect_owner()); } } } fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'tcx>) { if let TraitItemKind::Fn(ref sig, _) = item.kind { - check_method(cx, sig.decl, item.owner_id.def_id, item.span, item.hir_id()); + check_method(cx, sig.decl, item.owner_id.def_id, item.span, item.owner_id); } } } diff --git a/src/tools/clippy/clippy_lints/src/returns.rs b/src/tools/clippy/clippy_lints/src/returns.rs index bbbd9e498..f0d7dd23a 100644 --- a/src/tools/clippy/clippy_lints/src/returns.rs +++ b/src/tools/clippy/clippy_lints/src/returns.rs @@ -1,18 +1,20 @@ use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::source::{snippet_opt, snippet_with_context}; use clippy_utils::visitors::{for_each_expr, Descend}; -use clippy_utils::{fn_def_id, path_to_local_id}; +use clippy_utils::{fn_def_id, path_to_local_id, span_find_starting_semi}; use core::ops::ControlFlow; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, HirId, LangItem, MatchSource, PatKind, QPath, StmtKind}; +use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, LangItem, MatchSource, PatKind, QPath, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::subst::GenericArgKind; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Span; use rustc_span::{BytePos, Pos}; +use std::borrow::Cow; declare_clippy_lint! { /// ### What it does @@ -68,31 +70,41 @@ declare_clippy_lint! { "using a return statement like `return expr;` where an expression would suffice" } -#[derive(PartialEq, Eq, Copy, Clone)] -enum RetReplacement { +#[derive(PartialEq, Eq, Clone)] +enum RetReplacement<'tcx> { Empty, Block, Unit, + IfSequence(Cow<'tcx, str>, Applicability), + Expr(Cow<'tcx, str>, Applicability), } -impl RetReplacement { +impl<'tcx> RetReplacement<'tcx> { fn sugg_help(self) -> &'static str { match self { - Self::Empty => "remove `return`", + Self::Empty | Self::Expr(..) => "remove `return`", Self::Block => "replace `return` with an empty block", Self::Unit => "replace `return` with a unit value", + Self::IfSequence(..) => "remove `return` and wrap the sequence with parentheses", + } + } + fn applicability(&self) -> Option<Applicability> { + match self { + Self::Expr(_, ap) | Self::IfSequence(_, ap) => Some(*ap), + _ => None, } } } -impl ToString for RetReplacement { +impl<'tcx> ToString for RetReplacement<'tcx> { fn to_string(&self) -> String { - match *self { - Self::Empty => "", - Self::Block => "{}", - Self::Unit => "()", + match self { + Self::Empty => String::new(), + Self::Block => "{}".to_string(), + Self::Unit => "()".to_string(), + Self::IfSequence(inner, _) => format!("({inner})"), + Self::Expr(inner, _) => inner.to_string(), } - .to_string() } } @@ -151,8 +163,8 @@ impl<'tcx> LateLintPass<'tcx> for Return { kind: FnKind<'tcx>, _: &'tcx FnDecl<'tcx>, body: &'tcx Body<'tcx>, - _: Span, - _: HirId, + sp: Span, + _: LocalDefId, ) { match kind { FnKind::Closure => { @@ -166,14 +178,14 @@ impl<'tcx> LateLintPass<'tcx> for Return { check_final_expr(cx, body.value, vec![], replacement); }, FnKind::ItemFn(..) | FnKind::Method(..) => { - check_block_return(cx, &body.value.kind, vec![]); + check_block_return(cx, &body.value.kind, sp, vec![]); }, } } } // if `expr` is a block, check if there are needless returns in it -fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, semi_spans: Vec<Span>) { +fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, sp: Span, mut semi_spans: Vec<Span>) { if let ExprKind::Block(block, _) = expr_kind { if let Some(block_expr) = block.expr { check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty); @@ -183,12 +195,14 @@ fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, check_final_expr(cx, expr, semi_spans, RetReplacement::Empty); }, StmtKind::Semi(semi_expr) => { - let mut semi_spans_and_this_one = semi_spans; - // we only want the span containing the semicolon so we can remove it later. From `entry.rs:382` - if let Some(semicolon_span) = stmt.span.trim_start(semi_expr.span) { - semi_spans_and_this_one.push(semicolon_span); - check_final_expr(cx, semi_expr, semi_spans_and_this_one, RetReplacement::Empty); + // Remove ending semicolons and any whitespace ' ' in between. + // Without `return`, the suggestion might not compile if the semicolon is retained + if let Some(semi_span) = stmt.span.trim_start(semi_expr.span) { + let semi_span_to_remove = + span_find_starting_semi(cx.sess().source_map(), semi_span.with_hi(sp.hi())); + semi_spans.push(semi_span_to_remove); } + check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty); }, _ => (), } @@ -201,19 +215,38 @@ fn check_final_expr<'tcx>( expr: &'tcx Expr<'tcx>, semi_spans: Vec<Span>, /* containing all the places where we would need to remove semicolons if finding an * needless return */ - replacement: RetReplacement, + replacement: RetReplacement<'tcx>, ) { let peeled_drop_expr = expr.peel_drop_temps(); match &peeled_drop_expr.kind { // simple return is always "bad" ExprKind::Ret(ref inner) => { - // if desugar of `do yeet`, don't lint - if let Some(inner_expr) = inner - && let ExprKind::Call(path_expr, _) = inner_expr.kind - && let ExprKind::Path(QPath::LangItem(LangItem::TryTraitFromYeet, _, _)) = path_expr.kind - { - return; - } + // check if expr return nothing + let ret_span = if inner.is_none() && replacement == RetReplacement::Empty { + extend_span_to_previous_non_ws(cx, peeled_drop_expr.span) + } else { + peeled_drop_expr.span + }; + + let replacement = if let Some(inner_expr) = inner { + // if desugar of `do yeet`, don't lint + if let ExprKind::Call(path_expr, _) = inner_expr.kind + && let ExprKind::Path(QPath::LangItem(LangItem::TryTraitFromYeet, _, _)) = path_expr.kind + { + return; + } + + let mut applicability = Applicability::MachineApplicable; + let (snippet, _) = snippet_with_context(cx, inner_expr.span, ret_span.ctxt(), "..", &mut applicability); + if expr_contains_conjunctive_ifs(inner_expr) { + RetReplacement::IfSequence(snippet, applicability) + } else { + RetReplacement::Expr(snippet, applicability) + } + } else { + replacement + }; + if !cx.tcx.hir().attrs(expr.hir_id).is_empty() { return; } @@ -221,19 +254,13 @@ fn check_final_expr<'tcx>( if borrows { return; } - // check if expr return nothing - let ret_span = if inner.is_none() && replacement == RetReplacement::Empty { - extend_span_to_previous_non_ws(cx, peeled_drop_expr.span) - } else { - peeled_drop_expr.span - }; - emit_return_lint(cx, ret_span, semi_spans, inner.as_ref().map(|i| i.span), replacement); + emit_return_lint(cx, ret_span, semi_spans, replacement); }, ExprKind::If(_, then, else_clause_opt) => { - check_block_return(cx, &then.kind, semi_spans.clone()); + check_block_return(cx, &then.kind, peeled_drop_expr.span, semi_spans.clone()); if let Some(else_clause) = else_clause_opt { - check_block_return(cx, &else_clause.kind, semi_spans); + check_block_return(cx, &else_clause.kind, peeled_drop_expr.span, semi_spans); } }, // a match expr, check all arms @@ -246,33 +273,29 @@ fn check_final_expr<'tcx>( } }, // if it's a whole block, check it - other_expr_kind => check_block_return(cx, other_expr_kind, semi_spans), + other_expr_kind => check_block_return(cx, other_expr_kind, peeled_drop_expr.span, semi_spans), } } -fn emit_return_lint( - cx: &LateContext<'_>, - ret_span: Span, - semi_spans: Vec<Span>, - inner_span: Option<Span>, - replacement: RetReplacement, -) { +fn expr_contains_conjunctive_ifs<'tcx>(expr: &'tcx Expr<'tcx>) -> bool { + fn contains_if(expr: &Expr<'_>, on_if: bool) -> bool { + match expr.kind { + ExprKind::If(..) => on_if, + ExprKind::Binary(_, left, right) => contains_if(left, true) || contains_if(right, true), + _ => false, + } + } + + contains_if(expr, false) +} + +fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, semi_spans: Vec<Span>, replacement: RetReplacement<'_>) { if ret_span.from_expansion() { return; } - let mut applicability = Applicability::MachineApplicable; - let return_replacement = inner_span.map_or_else( - || replacement.to_string(), - |inner_span| { - let (snippet, _) = snippet_with_context(cx, inner_span, ret_span.ctxt(), "..", &mut applicability); - snippet.to_string() - }, - ); - let sugg_help = if inner_span.is_some() { - "remove `return`" - } else { - replacement.sugg_help() - }; + let applicability = replacement.applicability().unwrap_or(Applicability::MachineApplicable); + let return_replacement = replacement.to_string(); + let sugg_help = replacement.sugg_help(); span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| { diag.span_suggestion_hidden(ret_span, sugg_help, return_replacement, applicability); // for each parent statement, we need to remove the semicolon @@ -288,6 +311,7 @@ fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) && cx .tcx .fn_sig(def_id) + .subst_identity() .skip_binder() .output() .walk() diff --git a/src/tools/clippy/clippy_lints/src/same_name_method.rs b/src/tools/clippy/clippy_lints/src/same_name_method.rs index 17763128c..a37e2772d 100644 --- a/src/tools/clippy/clippy_lints/src/same_name_method.rs +++ b/src/tools/clippy/clippy_lints/src/same_name_method.rs @@ -52,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { let mut map = FxHashMap::<Res, ExistingName>::default(); for id in cx.tcx.hir().items() { - if matches!(cx.tcx.def_kind(id.owner_id), DefKind::Impl) + if matches!(cx.tcx.def_kind(id.owner_id), DefKind::Impl { .. }) && let item = cx.tcx.hir().item(id) && let ItemKind::Impl(Impl { items, diff --git a/src/tools/clippy/clippy_lints/src/self_named_constructors.rs b/src/tools/clippy/clippy_lints/src/self_named_constructors.rs index 71b387c66..beca203c8 100644 --- a/src/tools/clippy/clippy_lints/src/self_named_constructors.rs +++ b/src/tools/clippy/clippy_lints/src/self_named_constructors.rs @@ -53,8 +53,8 @@ impl<'tcx> LateLintPass<'tcx> for SelfNamedConstructors { let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id()).def_id; let item = cx.tcx.hir().expect_item(parent); - let self_ty = cx.tcx.type_of(item.owner_id); - let ret_ty = return_ty(cx, impl_item.hir_id()); + let self_ty = cx.tcx.type_of(item.owner_id).subst_identity(); + let ret_ty = return_ty(cx, impl_item.owner_id); // Do not check trait impls if matches!(item.kind, ItemKind::Impl(Impl { of_trait: Some(_), .. })) { diff --git a/src/tools/clippy/clippy_lints/src/semicolon_block.rs b/src/tools/clippy/clippy_lints/src/semicolon_block.rs index 8f1d1490e..34a3e5ddf 100644 --- a/src/tools/clippy/clippy_lints/src/semicolon_block.rs +++ b/src/tools/clippy/clippy_lints/src/semicolon_block.rs @@ -30,7 +30,7 @@ declare_clippy_lint! { /// # let x = 0; /// unsafe { f(x); } /// ``` - #[clippy::version = "1.66.0"] + #[clippy::version = "1.68.0"] pub SEMICOLON_INSIDE_BLOCK, restriction, "add a semicolon inside the block" @@ -59,7 +59,7 @@ declare_clippy_lint! { /// # let x = 0; /// unsafe { f(x) }; /// ``` - #[clippy::version = "1.66.0"] + #[clippy::version = "1.68.0"] pub SEMICOLON_OUTSIDE_BLOCK, restriction, "add a semicolon outside the block" diff --git a/src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs b/src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs new file mode 100644 index 000000000..c3e99aa00 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs @@ -0,0 +1,423 @@ +use clippy_utils::{ + diagnostics::span_lint_and_then, + get_attr, + source::{indent_of, snippet}, +}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::{Applicability, Diagnostic}; +use rustc_hir::{ + self as hir, + intravisit::{walk_expr, Visitor}, +}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::ty::{subst::GenericArgKind, Ty, TypeAndMut}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{symbol::Ident, Span, DUMMY_SP}; + +declare_clippy_lint! { + /// ### What it does + /// + /// Searches for elements marked with `#[clippy::significant_drop]` that could be early + /// dropped but are in fact dropped at the end of their scopes. In other words, enforces the + /// "tightening" of their possible lifetimes. + /// + /// ### Why is this bad? + /// + /// Elements marked with `#[clippy::has_significant_drop]` are generally synchronizing + /// primitives that manage shared resources, as such, it is desired to release them as soon as + /// possible to avoid unnecessary resource contention. + /// + /// ### Example + /// + /// ```rust,ignore + /// fn main() { + /// let lock = some_sync_resource.lock(); + /// let owned_rslt = lock.do_stuff_with_resource(); + /// // Only `owned_rslt` is needed but `lock` is still held. + /// do_heavy_computation_that_takes_time(owned_rslt); + /// } + /// ``` + /// + /// Use instead: + /// + /// ```rust,ignore + /// fn main() { + /// let owned_rslt = some_sync_resource.lock().do_stuff_with_resource(); + /// do_heavy_computation_that_takes_time(owned_rslt); + /// } + /// ``` + #[clippy::version = "1.67.0"] + pub SIGNIFICANT_DROP_TIGHTENING, + nursery, + "Searches for elements marked with `#[clippy::has_significant_drop]` that could be early dropped but are in fact dropped at the end of their scopes" +} + +impl_lint_pass!(SignificantDropTightening<'_> => [SIGNIFICANT_DROP_TIGHTENING]); + +#[derive(Default)] +pub struct SignificantDropTightening<'tcx> { + /// Auxiliary structure used to avoid having to verify the same type multiple times. + seen_types: FxHashSet<Ty<'tcx>>, + type_cache: FxHashMap<Ty<'tcx>, bool>, +} + +impl<'tcx> SignificantDropTightening<'tcx> { + /// Unifies the statements of a block with its return expression. + fn all_block_stmts<'ret, 'rslt, 'stmts>( + block_stmts: &'stmts [hir::Stmt<'tcx>], + dummy_ret_stmt: Option<&'ret hir::Stmt<'tcx>>, + ) -> impl Iterator<Item = &'rslt hir::Stmt<'tcx>> + where + 'ret: 'rslt, + 'stmts: 'rslt, + { + block_stmts.iter().chain(dummy_ret_stmt) + } + + /// Searches for at least one statement that could slow down the release of a significant drop. + fn at_least_one_stmt_is_expensive<'stmt>(stmts: impl Iterator<Item = &'stmt hir::Stmt<'tcx>>) -> bool + where + 'tcx: 'stmt, + { + for stmt in stmts { + match stmt.kind { + hir::StmtKind::Expr(expr) if let hir::ExprKind::Path(_) = expr.kind => {} + hir::StmtKind::Local(local) if let Some(expr) = local.init + && let hir::ExprKind::Path(_) = expr.kind => {}, + _ => return true + }; + } + false + } + + /// Verifies if the expression is of type `drop(some_lock_path)` to assert that the temporary + /// is already being dropped before the end of its scope. + fn has_drop(expr: &'tcx hir::Expr<'_>, init_bind_ident: Ident) -> bool { + if let hir::ExprKind::Call(fun, args) = expr.kind + && let hir::ExprKind::Path(hir::QPath::Resolved(_, fun_path)) = &fun.kind + && let [fun_ident, ..] = fun_path.segments + && fun_ident.ident.name == rustc_span::sym::drop + && let [first_arg, ..] = args + && let hir::ExprKind::Path(hir::QPath::Resolved(_, arg_path)) = &first_arg.kind + && let [first_arg_ps, .. ] = arg_path.segments + { + first_arg_ps.ident == init_bind_ident + } + else { + false + } + } + + /// Tries to find types marked with `#[has_significant_drop]` of an expression `expr` that is + /// originated from `stmt` and then performs common logic on `sdap`. + fn modify_sdap_if_sig_drop_exists( + &mut self, + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + idx: usize, + sdap: &mut SigDropAuxParams, + stmt: &hir::Stmt<'_>, + cb: impl Fn(&mut SigDropAuxParams), + ) { + let mut sig_drop_finder = SigDropFinder::new(cx, &mut self.seen_types, &mut self.type_cache); + sig_drop_finder.visit_expr(expr); + if sig_drop_finder.has_sig_drop { + cb(sdap); + if sdap.number_of_stmts > 0 { + sdap.last_use_stmt_idx = idx; + sdap.last_use_stmt_span = stmt.span; + if let hir::ExprKind::MethodCall(_, _, _, span) = expr.kind { + sdap.last_use_method_span = span; + } + } + sdap.number_of_stmts = sdap.number_of_stmts.wrapping_add(1); + } + } + + /// Shows generic overall messages as well as specialized messages depending on the usage. + fn set_suggestions(cx: &LateContext<'tcx>, block_span: Span, diag: &mut Diagnostic, sdap: &SigDropAuxParams) { + match sdap.number_of_stmts { + 0 | 1 => {}, + 2 => { + let indent = " ".repeat(indent_of(cx, sdap.last_use_stmt_span).unwrap_or(0)); + let init_method = snippet(cx, sdap.init_method_span, ".."); + let usage_method = snippet(cx, sdap.last_use_method_span, ".."); + let stmt = if let Some(last_use_bind_span) = sdap.last_use_bind_span { + format!( + "\n{indent}let {} = {init_method}.{usage_method};", + snippet(cx, last_use_bind_span, ".."), + ) + } else { + format!("\n{indent}{init_method}.{usage_method};") + }; + diag.span_suggestion_verbose( + sdap.init_stmt_span, + "merge the temporary construction with its single usage", + stmt, + Applicability::MaybeIncorrect, + ); + diag.span_suggestion( + sdap.last_use_stmt_span, + "remove separated single usage", + "", + Applicability::MaybeIncorrect, + ); + }, + _ => { + diag.span_suggestion( + sdap.last_use_stmt_span.shrink_to_hi(), + "drop the temporary after the end of its last usage", + format!( + "\n{}drop({});", + " ".repeat(indent_of(cx, sdap.last_use_stmt_span).unwrap_or(0)), + sdap.init_bind_ident + ), + Applicability::MaybeIncorrect, + ); + }, + } + diag.note("this might lead to unnecessary resource contention"); + diag.span_label( + block_span, + format!( + "temporary `{}` is currently being dropped at the end of its contained scope", + sdap.init_bind_ident + ), + ); + } +} + +impl<'tcx> LateLintPass<'tcx> for SignificantDropTightening<'tcx> { + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { + let dummy_ret_stmt = block.expr.map(|expr| hir::Stmt { + hir_id: hir::HirId::INVALID, + kind: hir::StmtKind::Expr(expr), + span: DUMMY_SP, + }); + let mut sdap = SigDropAuxParams::default(); + for (idx, stmt) in Self::all_block_stmts(block.stmts, dummy_ret_stmt.as_ref()).enumerate() { + match stmt.kind { + hir::StmtKind::Expr(expr) => self.modify_sdap_if_sig_drop_exists( + cx, + expr, + idx, + &mut sdap, + stmt, + |_| {} + ), + hir::StmtKind::Local(local) if let Some(expr) = local.init => self.modify_sdap_if_sig_drop_exists( + cx, + expr, + idx, + &mut sdap, + stmt, + |local_sdap| { + if local_sdap.number_of_stmts == 0 { + if let hir::PatKind::Binding(_, _, ident, _) = local.pat.kind { + local_sdap.init_bind_ident = ident; + } + if let hir::ExprKind::MethodCall(_, local_expr, _, span) = expr.kind { + local_sdap.init_method_span = local_expr.span.to(span); + } + local_sdap.init_stmt_span = stmt.span; + } + else if let hir::PatKind::Binding(_, _, ident, _) = local.pat.kind { + local_sdap.last_use_bind_span = Some(ident.span); + } + } + ), + hir::StmtKind::Semi(expr) => { + if Self::has_drop(expr, sdap.init_bind_ident) { + return; + } + self.modify_sdap_if_sig_drop_exists(cx, expr, idx, &mut sdap, stmt, |_| {}); + }, + _ => {} + }; + } + + let idx = sdap.last_use_stmt_idx.wrapping_add(1); + let stmts_after_last_use = Self::all_block_stmts(block.stmts, dummy_ret_stmt.as_ref()).skip(idx); + if sdap.number_of_stmts > 1 && Self::at_least_one_stmt_is_expensive(stmts_after_last_use) { + span_lint_and_then( + cx, + SIGNIFICANT_DROP_TIGHTENING, + sdap.init_bind_ident.span, + "temporary with significant `Drop` can be early dropped", + |diag| { + Self::set_suggestions(cx, block.span, diag, &sdap); + }, + ); + } + } +} + +/// Auxiliary parameters used on each block check. +struct SigDropAuxParams { + /// The binding or variable that references the initial construction of the type marked with + /// `#[has_significant_drop]`. + init_bind_ident: Ident, + /// Similar to `init_bind_ident` but encompasses the right-hand method call. + init_method_span: Span, + /// Similar to `init_bind_ident` but encompasses the whole contained statement. + init_stmt_span: Span, + + /// The last visited binding or variable span within a block that had any referenced inner type + /// marked with `#[has_significant_drop]`. + last_use_bind_span: Option<Span>, + /// Index of the last visited statement within a block that had any referenced inner type + /// marked with `#[has_significant_drop]`. + last_use_stmt_idx: usize, + /// Similar to `last_use_bind_span` but encompasses the whole contained statement. + last_use_stmt_span: Span, + /// Similar to `last_use_bind_span` but encompasses the right-hand method call. + last_use_method_span: Span, + + /// Total number of statements within a block that have any referenced inner type marked with + /// `#[has_significant_drop]`. + number_of_stmts: usize, +} + +impl Default for SigDropAuxParams { + fn default() -> Self { + Self { + init_bind_ident: Ident::empty(), + init_method_span: DUMMY_SP, + init_stmt_span: DUMMY_SP, + last_use_bind_span: None, + last_use_method_span: DUMMY_SP, + last_use_stmt_idx: 0, + last_use_stmt_span: DUMMY_SP, + number_of_stmts: 0, + } + } +} + +/// Checks the existence of the `#[has_significant_drop]` attribute +struct SigDropChecker<'cx, 'sdt, 'tcx> { + cx: &'cx LateContext<'tcx>, + seen_types: &'sdt mut FxHashSet<Ty<'tcx>>, + type_cache: &'sdt mut FxHashMap<Ty<'tcx>, bool>, +} + +impl<'cx, 'sdt, 'tcx> SigDropChecker<'cx, 'sdt, 'tcx> { + pub(crate) fn new( + cx: &'cx LateContext<'tcx>, + seen_types: &'sdt mut FxHashSet<Ty<'tcx>>, + type_cache: &'sdt mut FxHashMap<Ty<'tcx>, bool>, + ) -> Self { + seen_types.clear(); + Self { + cx, + seen_types, + type_cache, + } + } + + pub(crate) fn has_sig_drop_attr_uncached(&mut self, ty: Ty<'tcx>) -> bool { + if let Some(adt) = ty.ty_adt_def() { + let mut iter = get_attr( + self.cx.sess(), + self.cx.tcx.get_attrs_unchecked(adt.did()), + "has_significant_drop", + ); + if iter.next().is_some() { + return true; + } + } + match ty.kind() { + rustc_middle::ty::Adt(a, b) => { + for f in a.all_fields() { + let ty = f.ty(self.cx.tcx, b); + if !self.has_seen_ty(ty) && self.has_sig_drop_attr(ty) { + return true; + } + } + for generic_arg in b.iter() { + if let GenericArgKind::Type(ty) = generic_arg.unpack() { + if self.has_sig_drop_attr(ty) { + return true; + } + } + } + false + }, + rustc_middle::ty::Array(ty, _) + | rustc_middle::ty::RawPtr(TypeAndMut { ty, .. }) + | rustc_middle::ty::Ref(_, ty, _) + | rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(*ty), + _ => false, + } + } + + pub(crate) fn has_sig_drop_attr(&mut self, ty: Ty<'tcx>) -> bool { + // The borrow checker prevents us from using something fancier like or_insert_with. + if let Some(ty) = self.type_cache.get(&ty) { + return *ty; + } + let value = self.has_sig_drop_attr_uncached(ty); + self.type_cache.insert(ty, value); + value + } + + fn has_seen_ty(&mut self, ty: Ty<'tcx>) -> bool { + !self.seen_types.insert(ty) + } +} + +/// Performs recursive calls to find any inner type marked with `#[has_significant_drop]`. +struct SigDropFinder<'cx, 'sdt, 'tcx> { + cx: &'cx LateContext<'tcx>, + has_sig_drop: bool, + sig_drop_checker: SigDropChecker<'cx, 'sdt, 'tcx>, +} + +impl<'cx, 'sdt, 'tcx> SigDropFinder<'cx, 'sdt, 'tcx> { + fn new( + cx: &'cx LateContext<'tcx>, + seen_types: &'sdt mut FxHashSet<Ty<'tcx>>, + type_cache: &'sdt mut FxHashMap<Ty<'tcx>, bool>, + ) -> Self { + Self { + cx, + has_sig_drop: false, + sig_drop_checker: SigDropChecker::new(cx, seen_types, type_cache), + } + } +} + +impl<'cx, 'sdt, 'tcx> Visitor<'tcx> for SigDropFinder<'cx, 'sdt, 'tcx> { + fn visit_expr(&mut self, ex: &'tcx hir::Expr<'_>) { + if self + .sig_drop_checker + .has_sig_drop_attr(self.cx.typeck_results().expr_ty(ex)) + { + self.has_sig_drop = true; + return; + } + + match ex.kind { + hir::ExprKind::MethodCall(_, expr, ..) => { + self.visit_expr(expr); + }, + hir::ExprKind::Array(..) + | hir::ExprKind::Assign(..) + | hir::ExprKind::AssignOp(..) + | hir::ExprKind::Binary(..) + | hir::ExprKind::Box(..) + | hir::ExprKind::Call(..) + | hir::ExprKind::Field(..) + | hir::ExprKind::If(..) + | hir::ExprKind::Index(..) + | hir::ExprKind::Match(..) + | hir::ExprKind::Repeat(..) + | hir::ExprKind::Ret(..) + | hir::ExprKind::Tup(..) + | hir::ExprKind::Unary(..) + | hir::ExprKind::Yield(..) => { + walk_expr(self, ex); + }, + _ => {}, + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/strings.rs b/src/tools/clippy/clippy_lints/src/strings.rs index bc18cad6d..b2f4b3109 100644 --- a/src/tools/clippy/clippy_lints/src/strings.rs +++ b/src/tools/clippy/clippy_lints/src/strings.rs @@ -190,7 +190,7 @@ impl<'tcx> LateLintPass<'tcx> for StringAdd { }, ExprKind::Index(target, _idx) => { let e_ty = cx.typeck_results().expr_ty(target).peel_refs(); - if matches!(e_ty.kind(), ty::Str) || is_type_lang_item(cx, e_ty, LangItem::String) { + if e_ty.is_str() || is_type_lang_item(cx, e_ty, LangItem::String) { span_lint( cx, STRING_SLICE, @@ -407,7 +407,7 @@ impl<'tcx> LateLintPass<'tcx> for StrToString { if path.ident.name == sym::to_string; let ty = cx.typeck_results().expr_ty(self_arg); if let ty::Ref(_, ty, ..) = ty.kind(); - if *ty.kind() == ty::Str; + if ty.is_str(); then { span_lint_and_help( cx, diff --git a/src/tools/clippy/clippy_lints/src/suspicious_xor_used_as_pow.rs b/src/tools/clippy/clippy_lints/src/suspicious_xor_used_as_pow.rs index 301aa5798..9c0dc8096 100644 --- a/src/tools/clippy/clippy_lints/src/suspicious_xor_used_as_pow.rs +++ b/src/tools/clippy/clippy_lints/src/suspicious_xor_used_as_pow.rs @@ -9,7 +9,7 @@ declare_clippy_lint! { /// ### What it does /// Warns for a Bitwise XOR (`^`) operator being probably confused as a powering. It will not trigger if any of the numbers are not in decimal. /// ### Why is this bad? - /// It's most probably a typo and may lead to unexpected behaviours. + /// It's most probably a typo and may lead to unexpected behaviours. /// ### Example /// ```rust /// let x = 3_i32 ^ 4_i32; @@ -18,7 +18,7 @@ declare_clippy_lint! { /// ```rust /// let x = 3_i32.pow(4); /// ``` - #[clippy::version = "1.66.0"] + #[clippy::version = "1.67.0"] pub SUSPICIOUS_XOR_USED_AS_POW, restriction, "XOR (`^`) operator possibly used as exponentiation operator" diff --git a/src/tools/clippy/clippy_lints/src/swap.rs b/src/tools/clippy/clippy_lints/src/swap.rs index 17e9cc5f6..0f062cecf 100644 --- a/src/tools/clippy/clippy_lints/src/swap.rs +++ b/src/tools/clippy/clippy_lints/src/swap.rs @@ -10,7 +10,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Spanned; -use rustc_span::{sym, Span}; +use rustc_span::{sym, symbol::Ident, Span}; declare_clippy_lint! { /// ### What it does @@ -174,53 +174,74 @@ fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) { /// Implementation of the `ALMOST_SWAPPED` lint. fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) { - for w in block.stmts.windows(2) { - if_chain! { - if let StmtKind::Semi(first) = w[0].kind; - if let StmtKind::Semi(second) = w[1].kind; - if first.span.ctxt() == second.span.ctxt(); - if let ExprKind::Assign(lhs0, rhs0, _) = first.kind; - if let ExprKind::Assign(lhs1, rhs1, _) = second.kind; - if eq_expr_value(cx, lhs0, rhs1); - if eq_expr_value(cx, lhs1, rhs0); - then { - let lhs0 = Sugg::hir_opt(cx, lhs0); - let rhs0 = Sugg::hir_opt(cx, rhs0); - let (what, lhs, rhs) = if let (Some(first), Some(second)) = (lhs0, rhs0) { - ( - format!(" `{first}` and `{second}`"), - first.mut_addr().to_string(), - second.mut_addr().to_string(), - ) - } else { - (String::new(), String::new(), String::new()) - }; + for [first, second] in block.stmts.array_windows() { + if let Some((lhs0, rhs0)) = parse(first) + && let Some((lhs1, rhs1)) = parse(second) + && first.span.eq_ctxt(second.span) + && is_same(cx, lhs0, rhs1) + && is_same(cx, lhs1, rhs0) + && let Some(lhs_sugg) = match &lhs0 { + ExprOrIdent::Expr(expr) => Sugg::hir_opt(cx, expr), + ExprOrIdent::Ident(ident) => Some(Sugg::NonParen(ident.as_str().into())), + } + && let Some(rhs_sugg) = Sugg::hir_opt(cx, rhs0) + { + let span = first.span.to(rhs1.span); + let Some(sugg) = std_or_core(cx) else { return }; + span_lint_and_then( + cx, + ALMOST_SWAPPED, + span, + &format!("this looks like you are trying to swap `{lhs_sugg}` and `{rhs_sugg}`"), + |diag| { + diag.span_suggestion( + span, + "try", + format!("{sugg}::mem::swap({}, {})", lhs_sugg.mut_addr(), rhs_sugg.mut_addr()), + Applicability::MaybeIncorrect, + ); + diag.note(format!("or maybe you should use `{sugg}::mem::replace`?")); + }, + ); + } + } +} + +fn is_same(cx: &LateContext<'_>, lhs: ExprOrIdent<'_>, rhs: &Expr<'_>) -> bool { + match lhs { + ExprOrIdent::Expr(expr) => eq_expr_value(cx, expr, rhs), + ExprOrIdent::Ident(ident) => { + if let ExprKind::Path(QPath::Resolved(None, path)) = rhs.kind + && let [segment] = &path.segments + && segment.ident == ident + { + true + } else { + false + } + } + } +} - let span = first.span.to(second.span); - let Some(sugg) = std_or_core(cx) else { return }; +#[derive(Debug, Clone, Copy)] +enum ExprOrIdent<'a> { + Expr(&'a Expr<'a>), + Ident(Ident), +} - span_lint_and_then(cx, - ALMOST_SWAPPED, - span, - &format!("this looks like you are trying to swap{what}"), - |diag| { - if !what.is_empty() { - diag.span_suggestion( - span, - "try", - format!( - "{sugg}::mem::swap({lhs}, {rhs})", - ), - Applicability::MaybeIncorrect, - ); - diag.note( - format!("or maybe you should use `{sugg}::mem::replace`?") - ); - } - }); +fn parse<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(ExprOrIdent<'hir>, &'a Expr<'hir>)> { + if let StmtKind::Semi(expr) = stmt.kind { + if let ExprKind::Assign(lhs, rhs, _) = expr.kind { + return Some((ExprOrIdent::Expr(lhs), rhs)); + } + } else if let StmtKind::Local(expr) = stmt.kind { + if let Some(rhs) = expr.init { + if let PatKind::Binding(_, _, ident_l, _) = expr.pat.kind { + return Some((ExprOrIdent::Ident(ident_l), rhs)); } } } + None } /// Implementation of the xor case for `MANUAL_SWAP` lint. diff --git a/src/tools/clippy/clippy_lints/src/trailing_empty_array.rs b/src/tools/clippy/clippy_lints/src/trailing_empty_array.rs index 63b326048..1382c1a40 100644 --- a/src/tools/clippy/clippy_lints/src/trailing_empty_array.rs +++ b/src/tools/clippy/clippy_lints/src/trailing_empty_array.rs @@ -61,9 +61,8 @@ fn is_struct_with_trailing_zero_sized_array(cx: &LateContext<'_>, item: &Item<'_ if let rustc_hir::TyKind::Array(_, rustc_hir::ArrayLen::Body(length)) = last_field.ty.kind; // Then check if that that array zero-sized - let length_ldid = cx.tcx.hir().local_def_id(length.hir_id); - let length = Const::from_anon_const(cx.tcx, length_ldid); - let length = length.try_eval_usize(cx.tcx, cx.param_env); + let length = Const::from_anon_const(cx.tcx, length.def_id); + let length = length.try_eval_target_usize(cx.tcx, cx.param_env); if let Some(length) = length; then { length == 0 diff --git a/src/tools/clippy/clippy_lints/src/transmute/mod.rs b/src/tools/clippy/clippy_lints/src/transmute/mod.rs index 691d759d7..c01cbe509 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/mod.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/mod.rs @@ -3,6 +3,7 @@ mod transmute_float_to_int; mod transmute_int_to_bool; mod transmute_int_to_char; mod transmute_int_to_float; +mod transmute_int_to_non_zero; mod transmute_null_to_fn; mod transmute_num_to_bytes; mod transmute_ptr_to_ptr; @@ -255,6 +256,31 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does + /// Checks for transmutes from integers to `NonZero*` types, and suggests their `new_unchecked` + /// method instead. + /// + /// ### Why is this bad? + /// Transmutes work on any types and thus might cause unsoundness when those types change + /// elsewhere. `new_unchecked` only works for the appropriate types instead. + /// + /// ### Example + /// ```rust + /// # use core::num::NonZeroU32; + /// let _non_zero: NonZeroU32 = unsafe { std::mem::transmute(123) }; + /// ``` + /// Use instead: + /// ```rust + /// # use core::num::NonZeroU32; + /// let _non_zero = unsafe { NonZeroU32::new_unchecked(123) }; + /// ``` + #[clippy::version = "1.69.0"] + pub TRANSMUTE_INT_TO_NON_ZERO, + complexity, + "transmutes from an integer to a non-zero wrapper" +} + +declare_clippy_lint! { + /// ### What it does /// Checks for transmutes from a float to an integer. /// /// ### Why is this bad? @@ -451,6 +477,7 @@ impl_lint_pass!(Transmute => [ TRANSMUTE_BYTES_TO_STR, TRANSMUTE_INT_TO_BOOL, TRANSMUTE_INT_TO_FLOAT, + TRANSMUTE_INT_TO_NON_ZERO, TRANSMUTE_FLOAT_TO_INT, TRANSMUTE_NUM_TO_BYTES, UNSOUND_COLLECTION_TRANSMUTE, @@ -479,7 +506,10 @@ impl<'tcx> LateLintPass<'tcx> for Transmute { // - char conversions (https://github.com/rust-lang/rust/issues/89259) let const_context = in_constant(cx, e.hir_id); - let from_ty = cx.typeck_results().expr_ty_adjusted(arg); + let (from_ty, from_ty_adjusted) = match cx.typeck_results().expr_adjustments(arg) { + [] => (cx.typeck_results().expr_ty(arg), false), + [.., a] => (a.target, true), + }; // Adjustments for `to_ty` happen after the call to `transmute`, so don't use them. let to_ty = cx.typeck_results().expr_ty(e); @@ -498,6 +528,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute { | transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg) | transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg) | transmute_int_to_float::check(cx, e, from_ty, to_ty, arg, const_context) + | transmute_int_to_non_zero::check(cx, e, from_ty, to_ty, arg) | transmute_float_to_int::check(cx, e, from_ty, to_ty, arg, const_context) | transmute_num_to_bytes::check(cx, e, from_ty, to_ty, arg, const_context) | ( @@ -506,7 +537,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute { ); if !linted { - transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, to_ty, arg); + transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, from_ty_adjusted, to_ty, arg); } } } diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_non_zero.rs b/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_non_zero.rs new file mode 100644 index 000000000..550365325 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_non_zero.rs @@ -0,0 +1,61 @@ +use super::TRANSMUTE_INT_TO_NON_ZERO; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::sugg; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::{ + query::Key, + ty::{self, Ty}, +}; +use rustc_span::symbol::sym; + +/// Checks for `transmute_int_to_non_zero` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + arg: &'tcx Expr<'_>, +) -> bool { + let (ty::Int(_) | ty::Uint(_), Some(to_ty_id)) = (&from_ty.kind(), to_ty.ty_adt_id()) else { + return false; + }; + let Some(to_type_sym) = cx.tcx.get_diagnostic_name(to_ty_id) else { + return false; + }; + + if !matches!( + to_type_sym, + sym::NonZeroU8 + | sym::NonZeroU16 + | sym::NonZeroU32 + | sym::NonZeroU64 + | sym::NonZeroU128 + | sym::NonZeroI8 + | sym::NonZeroI16 + | sym::NonZeroI32 + | sym::NonZeroI64 + | sym::NonZeroI128 + ) { + return false; + } + + span_lint_and_then( + cx, + TRANSMUTE_INT_TO_NON_ZERO, + e.span, + &format!("transmute from a `{from_ty}` to a `{to_type_sym}`"), + |diag| { + let arg = sugg::Sugg::hir(cx, arg, ".."); + diag.span_suggestion( + e.span, + "consider using", + format!("{to_type_sym}::{}({arg})", sym::new_unchecked), + Applicability::Unspecified, + ); + }, + ); + true +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmute_ptr_to_ref.rs b/src/tools/clippy/clippy_lints/src/transmute/transmute_ptr_to_ref.rs index 54ac04df1..6bdb9aa5a 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/transmute_ptr_to_ref.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/transmute_ptr_to_ref.rs @@ -6,7 +6,7 @@ use clippy_utils::sugg; use rustc_errors::Applicability; use rustc_hir::{self as hir, Expr, GenericArg, Mutability, Path, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty, TypeVisitable}; +use rustc_middle::ty::{self, Ty, TypeVisitableExt}; /// Checks for `transmute_ptr_to_ref` lint. /// Returns `true` if it's triggered, otherwise returns `false`. diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmute_ref_to_ref.rs b/src/tools/clippy/clippy_lints/src/transmute/transmute_ref_to_ref.rs index afb7f2e13..426c72538 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/transmute_ref_to_ref.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/transmute_ref_to_ref.rs @@ -22,7 +22,8 @@ pub(super) fn check<'tcx>( if let (ty::Ref(_, ty_from, from_mutbl), ty::Ref(_, ty_to, to_mutbl)) = (&from_ty.kind(), &to_ty.kind()) { if_chain! { - if let (&ty::Slice(slice_ty), &ty::Str) = (&ty_from.kind(), &ty_to.kind()); + if let ty::Slice(slice_ty) = *ty_from.kind(); + if ty_to.is_str(); if let ty::Uint(ty::UintTy::U8) = slice_ty.kind(); if from_mutbl == to_mutbl; then { diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmute_undefined_repr.rs b/src/tools/clippy/clippy_lints/src/transmute/transmute_undefined_repr.rs index af0242348..5e24213d0 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -273,7 +273,7 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> .non_enum_variant() .fields .iter() - .map(|f| cx.tcx.bound_type_of(f.did).subst(cx.tcx, substs)); + .map(|f| cx.tcx.type_of(f.did).subst(cx.tcx, substs)); let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else { return ReducedTy::TypeErasure { raw_ptr_only: false }; }; diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs b/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs index b79d4e915..8530b4324 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs @@ -1,11 +1,11 @@ -use super::utils::can_be_expressed_as_pointer_cast; +use super::utils::check_cast; use super::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS; -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::sugg; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::Ty; +use rustc_middle::ty::{cast::CastKind, Ty}; /// Checks for `transmutes_expressible_as_ptr_casts` lint. /// Returns `true` if it's triggered, otherwise returns `false`. @@ -13,24 +13,40 @@ pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, + from_ty_adjusted: bool, to_ty: Ty<'tcx>, arg: &'tcx Expr<'_>, ) -> bool { - if can_be_expressed_as_pointer_cast(cx, e, from_ty, to_ty) { - span_lint_and_then( - cx, - TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, - e.span, - &format!("transmute from `{from_ty}` to `{to_ty}` which could be expressed as a pointer cast instead"), - |diag| { - if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) { - let sugg = arg.as_ty(to_ty.to_string()).to_string(); - diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable); - } - }, - ); - true - } else { - false - } + use CastKind::{AddrPtrCast, ArrayPtrCast, FnPtrAddrCast, FnPtrPtrCast, PtrAddrCast, PtrPtrCast}; + let mut app = Applicability::MachineApplicable; + let sugg = match check_cast(cx, e, from_ty, to_ty) { + Some(PtrPtrCast | AddrPtrCast | ArrayPtrCast | FnPtrPtrCast | FnPtrAddrCast) => { + Sugg::hir_with_context(cx, arg, e.span.ctxt(), "..", &mut app) + .as_ty(to_ty.to_string()) + .to_string() + }, + Some(PtrAddrCast) if !from_ty_adjusted => Sugg::hir_with_context(cx, arg, e.span.ctxt(), "..", &mut app) + .as_ty(to_ty.to_string()) + .to_string(), + + // The only adjustments here would be ref-to-ptr and unsize coercions. The result of an unsize coercions can't + // be transmuted to a usize. For ref-to-ptr coercions, borrows need to be cast to a pointer before being cast to + // a usize. + Some(PtrAddrCast) => format!( + "{} as {to_ty}", + Sugg::hir_with_context(cx, arg, e.span.ctxt(), "..", &mut app).as_ty(from_ty) + ), + _ => return false, + }; + + span_lint_and_sugg( + cx, + TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, + e.span, + &format!("transmute from `{from_ty}` to `{to_ty}` which could be expressed as a pointer cast instead"), + "try", + sugg, + app, + ); + true } diff --git a/src/tools/clippy/clippy_lints/src/transmute/useless_transmute.rs b/src/tools/clippy/clippy_lints/src/transmute/useless_transmute.rs index 871c3fadb..56207fe76 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/useless_transmute.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/useless_transmute.rs @@ -4,7 +4,7 @@ use clippy_utils::sugg; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty, TypeVisitable}; +use rustc_middle::ty::{self, Ty, TypeVisitableExt}; /// Checks for `useless_transmute` lint. /// Returns `true` if it's triggered, otherwise returns `false`. diff --git a/src/tools/clippy/clippy_lints/src/transmute/utils.rs b/src/tools/clippy/clippy_lints/src/transmute/utils.rs index 49d863ec0..cddaf9450 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/utils.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/utils.rs @@ -20,33 +20,21 @@ pub(super) fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx } } -/// Check if the type conversion can be expressed as a pointer cast, instead of -/// a transmute. In certain cases, including some invalid casts from array -/// references to pointers, this may cause additional errors to be emitted and/or -/// ICE error messages. This function will panic if that occurs. -pub(super) fn can_be_expressed_as_pointer_cast<'tcx>( - cx: &LateContext<'tcx>, - e: &'tcx Expr<'_>, - from_ty: Ty<'tcx>, - to_ty: Ty<'tcx>, -) -> bool { - use CastKind::{AddrPtrCast, ArrayPtrCast, FnPtrAddrCast, FnPtrPtrCast, PtrAddrCast, PtrPtrCast}; - matches!( - check_cast(cx, e, from_ty, to_ty), - Some(PtrPtrCast | PtrAddrCast | AddrPtrCast | ArrayPtrCast | FnPtrPtrCast | FnPtrAddrCast) - ) -} - /// If a cast from `from_ty` to `to_ty` is valid, returns an Ok containing the kind of /// the cast. In certain cases, including some invalid casts from array references /// to pointers, this may cause additional errors to be emitted and/or ICE error /// messages. This function will panic if that occurs. -fn check_cast<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> Option<CastKind> { +pub(super) fn check_cast<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, +) -> Option<CastKind> { let hir_id = e.hir_id; let local_def_id = hir_id.owner.def_id; Inherited::build(cx.tcx, local_def_id).enter(|inherited| { - let fn_ctxt = FnCtxt::new(inherited, cx.param_env, hir_id); + let fn_ctxt = FnCtxt::new(inherited, cx.param_env, local_def_id); // If we already have errors, we can't be sure we can pointer cast. assert!( diff --git a/src/tools/clippy/clippy_lints/src/types/mod.rs b/src/tools/clippy/clippy_lints/src/types/mod.rs index 229478b7c..c1f228d5f 100644 --- a/src/tools/clippy/clippy_lints/src/types/mod.rs +++ b/src/tools/clippy/clippy_lints/src/types/mod.rs @@ -12,11 +12,12 @@ mod vec_box; use rustc_hir as hir; use rustc_hir::intravisit::FnKind; use rustc_hir::{ - Body, FnDecl, FnRetTy, GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Local, MutTy, QPath, TraitItem, + Body, FnDecl, FnRetTy, GenericArg, ImplItem, ImplItemKind, Item, ItemKind, Local, MutTy, QPath, TraitItem, TraitItemKind, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Span; declare_clippy_lint! { @@ -311,15 +312,27 @@ pub struct Types { impl_lint_pass!(Types => [BOX_COLLECTION, VEC_BOX, OPTION_OPTION, LINKEDLIST, BORROWED_BOX, REDUNDANT_ALLOCATION, RC_BUFFER, RC_MUTEX, TYPE_COMPLEXITY]); impl<'tcx> LateLintPass<'tcx> for Types { - fn check_fn(&mut self, cx: &LateContext<'_>, _: FnKind<'_>, decl: &FnDecl<'_>, _: &Body<'_>, _: Span, id: HirId) { - let is_in_trait_impl = - if let Some(hir::Node::Item(item)) = cx.tcx.hir().find_by_def_id(cx.tcx.hir().get_parent_item(id).def_id) { - matches!(item.kind, ItemKind::Impl(hir::Impl { of_trait: Some(_), .. })) - } else { - false - }; + fn check_fn( + &mut self, + cx: &LateContext<'_>, + _: FnKind<'_>, + decl: &FnDecl<'_>, + _: &Body<'_>, + _: Span, + def_id: LocalDefId, + ) { + let is_in_trait_impl = if let Some(hir::Node::Item(item)) = cx.tcx.hir().find_by_def_id( + cx.tcx + .hir() + .get_parent_item(cx.tcx.hir().local_def_id_to_hir_id(def_id)) + .def_id, + ) { + matches!(item.kind, ItemKind::Impl(hir::Impl { of_trait: Some(_), .. })) + } else { + false + }; - let is_exported = cx.effective_visibilities.is_exported(cx.tcx.hir().local_def_id(id)); + let is_exported = cx.effective_visibilities.is_exported(def_id); self.check_fn_decl( cx, @@ -379,9 +392,7 @@ impl<'tcx> LateLintPass<'tcx> for Types { } fn check_field_def(&mut self, cx: &LateContext<'_>, field: &hir::FieldDef<'_>) { - let is_exported = cx - .effective_visibilities - .is_exported(cx.tcx.hir().local_def_id(field.hir_id)); + let is_exported = cx.effective_visibilities.is_exported(field.def_id); self.check_ty( cx, diff --git a/src/tools/clippy/clippy_lints/src/types/redundant_allocation.rs b/src/tools/clippy/clippy_lints/src/types/redundant_allocation.rs index f9b9a66b5..f7adc9d35 100644 --- a/src/tools/clippy/clippy_lints/src/types/redundant_allocation.rs +++ b/src/tools/clippy/clippy_lints/src/types/redundant_allocation.rs @@ -5,7 +5,7 @@ use rustc_errors::Applicability; use rustc_hir::{self as hir, def_id::DefId, QPath, TyKind}; use rustc_hir_analysis::hir_ty_to_ty; use rustc_lint::LateContext; -use rustc_middle::ty::TypeVisitable; +use rustc_middle::ty::TypeVisitableExt; use rustc_span::symbol::sym; use super::{utils, REDUNDANT_ALLOCATION}; diff --git a/src/tools/clippy/clippy_lints/src/types/vec_box.rs b/src/tools/clippy/clippy_lints/src/types/vec_box.rs index 7a3c7cd8a..d3062f3d2 100644 --- a/src/tools/clippy/clippy_lints/src/types/vec_box.rs +++ b/src/tools/clippy/clippy_lints/src/types/vec_box.rs @@ -7,7 +7,7 @@ use rustc_hir::{self as hir, def_id::DefId, GenericArg, QPath, TyKind}; use rustc_hir_analysis::hir_ty_to_ty; use rustc_lint::LateContext; use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::TypeVisitable; +use rustc_middle::ty::TypeVisitableExt; use rustc_span::symbol::sym; use super::VEC_BOX; diff --git a/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs b/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs index 2e1b6d8d4..2920684ad 100644 --- a/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -263,6 +263,18 @@ fn expr_has_unnecessary_safety_comment<'tcx>( expr: &'tcx hir::Expr<'tcx>, comment_pos: BytePos, ) -> Option<Span> { + if cx.tcx.hir().parent_iter(expr.hir_id).any(|(_, ref node)| { + matches!( + node, + Node::Block(&Block { + rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided), + .. + }), + ) + }) { + return None; + } + // this should roughly be the reverse of `block_parents_have_safety_comment` if for_each_expr_with_closures(cx, expr, |expr| match expr.kind { hir::ExprKind::Block( diff --git a/src/tools/clippy/clippy_lints/src/unit_return_expecting_ord.rs b/src/tools/clippy/clippy_lints/src/unit_return_expecting_ord.rs index a138a4baa..289ca4e9b 100644 --- a/src/tools/clippy/clippy_lints/src/unit_return_expecting_ord.rs +++ b/src/tools/clippy/clippy_lints/src/unit_return_expecting_ord.rs @@ -76,7 +76,7 @@ fn get_projection_pred<'tcx>( fn get_args_to_check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Vec<(usize, String)> { let mut args_to_check = Vec::new(); if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { - let fn_sig = cx.tcx.fn_sig(def_id); + let fn_sig = cx.tcx.fn_sig(def_id).subst_identity(); let generics = cx.tcx.predicates_of(def_id); let fn_mut_preds = get_trait_predicates_for_trait_id(cx, generics, cx.tcx.lang_items().fn_mut_trait()); let ord_preds = get_trait_predicates_for_trait_id(cx, generics, cx.tcx.get_diagnostic_item(sym::Ord)); diff --git a/src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs b/src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs index ce9ebad8c..d6167a621 100644 --- a/src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs +++ b/src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs @@ -156,7 +156,7 @@ fn needs_inferred_result_ty( }, _ => return false, }; - let sig = cx.tcx.fn_sig(id).skip_binder(); + let sig = cx.tcx.fn_sig(id).subst_identity().skip_binder(); if let ty::Param(output_ty) = *sig.output().kind() { let args: Vec<&Expr<'_>> = if let Some(receiver) = receiver { std::iter::once(receiver).chain(args.iter()).collect() diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_owned_empty_strings.rs b/src/tools/clippy/clippy_lints/src/unnecessary_owned_empty_strings.rs index 9f207d32f..6e802794f 100644 --- a/src/tools/clippy/clippy_lints/src/unnecessary_owned_empty_strings.rs +++ b/src/tools/clippy/clippy_lints/src/unnecessary_owned_empty_strings.rs @@ -7,6 +7,7 @@ use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::sym; declare_clippy_lint! { /// ### What it does @@ -54,7 +55,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryOwnedEmptyStrings { ); } else { if_chain! { - if Some(fun_def_id) == cx.tcx.lang_items().from_fn(); + if cx.tcx.is_diagnostic_item(sym::from_fn, fun_def_id); if let [.., last_arg] = args; if let ExprKind::Lit(spanned) = &last_arg.kind; if let LitKind::Str(symbol, _) = spanned.node; diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs b/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs index 84ec0d0fb..8b0e0ce5a 100644 --- a/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs +++ b/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs @@ -5,10 +5,11 @@ use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::LangItem::{OptionSome, ResultOk}; -use rustc_hir::{Body, ExprKind, FnDecl, HirId, Impl, ItemKind, Node}; +use rustc_hir::{Body, ExprKind, FnDecl, Impl, ItemKind, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::def_id::LocalDefId; use rustc_span::symbol::sym; use rustc_span::Span; @@ -77,12 +78,11 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { fn_decl: &FnDecl<'tcx>, body: &Body<'tcx>, span: Span, - hir_id: HirId, + def_id: LocalDefId, ) { // Abort if public function/method or closure. match fn_kind { FnKind::ItemFn(..) | FnKind::Method(..) => { - let def_id = cx.tcx.hir().local_def_id(hir_id); if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) { return; } @@ -91,6 +91,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { } // Abort if the method is implementing a trait or of it a trait method. + let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id); if let Some(Node::Item(item)) = cx.tcx.hir().find_parent(hir_id) { if matches!( item.kind, @@ -101,17 +102,18 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { } // Get the wrapper and inner types, if can't, abort. - let (return_type_label, lang_item, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id).kind() { - if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()) { - ("Option", OptionSome, subst.type_at(0)) - } else if cx.tcx.is_diagnostic_item(sym::Result, adt_def.did()) { - ("Result", ResultOk, subst.type_at(0)) + let (return_type_label, lang_item, inner_type) = + if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id.expect_owner()).kind() { + if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()) { + ("Option", OptionSome, subst.type_at(0)) + } else if cx.tcx.is_diagnostic_item(sym::Result, adt_def.did()) { + ("Result", ResultOk, subst.type_at(0)) + } else { + return; + } } else { return; - } - } else { - return; - }; + }; // Check if all return expression respect the following condition and collect them. let mut suggs = Vec::new(); diff --git a/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs b/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs index 7355260ae..a57bf7ee8 100644 --- a/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs +++ b/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs @@ -12,9 +12,9 @@ use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::DUMMY_SP; - use std::cell::Cell; use std::mem; +use thin_vec::{thin_vec, ThinVec}; declare_clippy_lint! { /// ### What it does @@ -214,7 +214,7 @@ macro_rules! always_pat { /// Focus on `focus_idx` in `alternatives`, /// attempting to extend it with elements of the same constructor `C` /// in `alternatives[focus_idx + 1..]`. -fn transform_with_focus_on_idx(alternatives: &mut Vec<P<Pat>>, focus_idx: usize) -> bool { +fn transform_with_focus_on_idx(alternatives: &mut ThinVec<P<Pat>>, focus_idx: usize) -> bool { // Extract the kind; we'll need to make some changes in it. let mut focus_kind = mem::replace(&mut alternatives[focus_idx].kind, PatKind::Wild); // We'll focus on `alternatives[focus_idx]`, @@ -296,7 +296,7 @@ fn extend_with_struct_pat( fps1: &mut [ast::PatField], rest1: bool, start: usize, - alternatives: &mut Vec<P<Pat>>, + alternatives: &mut ThinVec<P<Pat>>, ) -> bool { (0..fps1.len()).any(|idx| { let pos_in_2 = Cell::new(None); // The element `k`. @@ -336,9 +336,9 @@ fn extend_with_struct_pat( fn extend_with_matching_product( targets: &mut [P<Pat>], start: usize, - alternatives: &mut Vec<P<Pat>>, + alternatives: &mut ThinVec<P<Pat>>, predicate: impl Fn(&PatKind, &[P<Pat>], usize) -> bool, - extract: impl Fn(PatKind) -> Vec<P<Pat>>, + extract: impl Fn(PatKind) -> ThinVec<P<Pat>>, ) -> bool { (0..targets.len()).any(|idx| { let tail_or = drain_matching( @@ -365,14 +365,14 @@ fn take_pat(from: &mut Pat) -> Pat { /// Extend `target` as an or-pattern with the alternatives /// in `tail_or` if there are any and return if there were. -fn extend_with_tail_or(target: &mut Pat, tail_or: Vec<P<Pat>>) -> bool { - fn extend(target: &mut Pat, mut tail_or: Vec<P<Pat>>) { +fn extend_with_tail_or(target: &mut Pat, tail_or: ThinVec<P<Pat>>) -> bool { + fn extend(target: &mut Pat, mut tail_or: ThinVec<P<Pat>>) { match target { // On an existing or-pattern in the target, append to it. Pat { kind: Or(ps), .. } => ps.append(&mut tail_or), // Otherwise convert the target to an or-pattern. target => { - let mut init_or = vec![P(take_pat(target))]; + let mut init_or = thin_vec![P(take_pat(target))]; init_or.append(&mut tail_or); target.kind = Or(init_or); }, @@ -391,26 +391,42 @@ fn extend_with_tail_or(target: &mut Pat, tail_or: Vec<P<Pat>>) -> bool { // Only elements beginning with `start` are considered for extraction. fn drain_matching( start: usize, - alternatives: &mut Vec<P<Pat>>, + alternatives: &mut ThinVec<P<Pat>>, predicate: impl Fn(&PatKind) -> bool, extract: impl Fn(PatKind) -> P<Pat>, -) -> Vec<P<Pat>> { - let mut tail_or = vec![]; +) -> ThinVec<P<Pat>> { + let mut tail_or = ThinVec::new(); let mut idx = 0; - for pat in alternatives.drain_filter(|p| { - // Check if we should extract, but only if `idx >= start`. + + // If `ThinVec` had the `drain_filter` method, this loop could be rewritten + // like so: + // + // for pat in alternatives.drain_filter(|p| { + // // Check if we should extract, but only if `idx >= start`. + // idx += 1; + // idx > start && predicate(&p.kind) + // }) { + // tail_or.push(extract(pat.into_inner().kind)); + // } + let mut i = 0; + while i < alternatives.len() { idx += 1; - idx > start && predicate(&p.kind) - }) { - tail_or.push(extract(pat.into_inner().kind)); + // Check if we should extract, but only if `idx >= start`. + if idx > start && predicate(&alternatives[i].kind) { + let pat = alternatives.remove(i); + tail_or.push(extract(pat.into_inner().kind)); + } else { + i += 1; + } } + tail_or } fn extend_with_matching( target: &mut Pat, start: usize, - alternatives: &mut Vec<P<Pat>>, + alternatives: &mut ThinVec<P<Pat>>, predicate: impl Fn(&PatKind) -> bool, extract: impl Fn(PatKind) -> P<Pat>, ) -> bool { diff --git a/src/tools/clippy/clippy_lints/src/unused_async.rs b/src/tools/clippy/clippy_lints/src/unused_async.rs index 3538bef6e..55651a28b 100644 --- a/src/tools/clippy/clippy_lints/src/unused_async.rs +++ b/src/tools/clippy/clippy_lints/src/unused_async.rs @@ -1,9 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_help; use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, Visitor}; -use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, YieldSource}; +use rustc_hir::{Body, Expr, ExprKind, FnDecl, YieldSource}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::def_id::LocalDefId; use rustc_span::Span; declare_clippy_lint! { @@ -66,11 +67,11 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync { fn_decl: &'tcx FnDecl<'tcx>, body: &Body<'tcx>, span: Span, - hir_id: HirId, + def_id: LocalDefId, ) { if !span.from_expansion() && fn_kind.asyncness().is_async() { let mut visitor = AsyncFnVisitor { cx, found_await: false }; - walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), hir_id); + walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), def_id); if !visitor.found_await { span_lint_and_help( cx, diff --git a/src/tools/clippy/clippy_lints/src/unused_io_amount.rs b/src/tools/clippy/clippy_lints/src/unused_io_amount.rs index 92053cec5..0e526c216 100644 --- a/src/tools/clippy/clippy_lints/src/unused_io_amount.rs +++ b/src/tools/clippy/clippy_lints/src/unused_io_amount.rs @@ -65,7 +65,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount { } }, hir::ExprKind::MethodCall(path, arg_0, ..) => match path.ident.as_str() { - "expect" | "unwrap" | "unwrap_or" | "unwrap_or_else" => { + "expect" | "unwrap" | "unwrap_or" | "unwrap_or_else" | "is_ok" | "is_err" => { check_map_error(cx, arg_0, expr); }, _ => (), diff --git a/src/tools/clippy/clippy_lints/src/unwrap.rs b/src/tools/clippy/clippy_lints/src/unwrap.rs index ea878043c..377d3fb6f 100644 --- a/src/tools/clippy/clippy_lints/src/unwrap.rs +++ b/src/tools/clippy/clippy_lints/src/unwrap.rs @@ -11,6 +11,7 @@ use rustc_middle::hir::nested_filter; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::Ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Span; use rustc_span::sym; @@ -312,7 +313,7 @@ impl<'tcx> LateLintPass<'tcx> for Unwrap { decl: &'tcx FnDecl<'_>, body: &'tcx Body<'_>, span: Span, - fn_id: HirId, + fn_id: LocalDefId, ) { if span.from_expansion() { return; diff --git a/src/tools/clippy/clippy_lints/src/unwrap_in_result.rs b/src/tools/clippy/clippy_lints/src/unwrap_in_result.rs index f3611d174..3a1845425 100644 --- a/src/tools/clippy/clippy_lints/src/unwrap_in_result.rs +++ b/src/tools/clippy/clippy_lints/src/unwrap_in_result.rs @@ -64,8 +64,8 @@ impl<'tcx> LateLintPass<'tcx> for UnwrapInResult { // first check if it's a method or function if let hir::ImplItemKind::Fn(ref _signature, _) = impl_item.kind; // checking if its return type is `result` or `option` - if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::Result) - || is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::Option); + if is_type_diagnostic_item(cx, return_ty(cx, impl_item.owner_id), sym::Result) + || is_type_diagnostic_item(cx, return_ty(cx, impl_item.owner_id), sym::Option); then { lint_impl_body(cx, impl_item.span, impl_item); } diff --git a/src/tools/clippy/clippy_lints/src/use_self.rs b/src/tools/clippy/clippy_lints/src/use_self.rs index 6ae9d9d63..e7c540006 100644 --- a/src/tools/clippy/clippy_lints/src/use_self.rs +++ b/src/tools/clippy/clippy_lints/src/use_self.rs @@ -146,7 +146,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { .associated_item(impl_item.owner_id) .trait_item_def_id .expect("impl method matches a trait method"); - let trait_method_sig = cx.tcx.fn_sig(trait_method); + let trait_method_sig = cx.tcx.fn_sig(trait_method).subst_identity(); let trait_method_sig = cx.tcx.erase_late_bound_regions(trait_method_sig); // `impl_inputs_outputs` is an iterator over the types (`hir::Ty`) declared in the @@ -218,7 +218,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { } else { hir_ty_to_ty(cx.tcx, hir_ty) }; - if same_type_and_consts(ty, cx.tcx.type_of(impl_id)); + if same_type_and_consts(ty, cx.tcx.type_of(impl_id).subst_identity()); then { span_lint(cx, hir_ty.span); } @@ -230,7 +230,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { if !expr.span.from_expansion(); if self.msrv.meets(msrvs::TYPE_ALIAS_ENUM_VARIANTS); if let Some(&StackItem::Check { impl_id, .. }) = self.stack.last(); - if cx.typeck_results().expr_ty(expr) == cx.tcx.type_of(impl_id); + if cx.typeck_results().expr_ty(expr) == cx.tcx.type_of(impl_id).subst_identity(); then {} else { return; } } match expr.kind { @@ -254,7 +254,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { if let PatKind::Path(QPath::Resolved(_, path)) | PatKind::TupleStruct(QPath::Resolved(_, path), _, _) | PatKind::Struct(QPath::Resolved(_, path), _, _) = pat.kind; - if cx.typeck_results().pat_ty(pat) == cx.tcx.type_of(impl_id); + if cx.typeck_results().pat_ty(pat) == cx.tcx.type_of(impl_id).subst_identity(); then { check_path(cx, path); } diff --git a/src/tools/clippy/clippy_lints/src/useless_conversion.rs b/src/tools/clippy/clippy_lints/src/useless_conversion.rs index a95e7b613..fede625f7 100644 --- a/src/tools/clippy/clippy_lints/src/useless_conversion.rs +++ b/src/tools/clippy/clippy_lints/src/useless_conversion.rs @@ -161,7 +161,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { } if_chain! { - if Some(def_id) == cx.tcx.lang_items().from_fn(); + if cx.tcx.is_diagnostic_item(sym::from_fn, def_id); if same_type_and_consts(a, b); then { diff --git a/src/tools/clippy/clippy_lints/src/utils/author.rs b/src/tools/clippy/clippy_lints/src/utils/author.rs index bd7daf077..c37e5bb67 100644 --- a/src/tools/clippy/clippy_lints/src/utils/author.rs +++ b/src/tools/clippy/clippy_lints/src/utils/author.rs @@ -588,7 +588,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { }, } }, - ExprKind::Err => kind!("Err"), + ExprKind::Err(_) => kind!("Err"), ExprKind::DropTemps(expr) => { bind!(self, expr); kind!("DropTemps({expr})"); diff --git a/src/tools/clippy/clippy_lints/src/utils/conf.rs b/src/tools/clippy/clippy_lints/src/utils/conf.rs index 3e7d0028c..1c7f3e96d 100644 --- a/src/tools/clippy/clippy_lints/src/utils/conf.rs +++ b/src/tools/clippy/clippy_lints/src/utils/conf.rs @@ -219,7 +219,8 @@ define_Conf! { /// /// #### Noteworthy /// - /// A type, say `SomeType`, listed in this configuration has the same behavior of `["SomeType" , "*"], ["*", "SomeType"]` in `arithmetic_side_effects_allowed_binary`. + /// A type, say `SomeType`, listed in this configuration has the same behavior of + /// `["SomeType" , "*"], ["*", "SomeType"]` in `arithmetic_side_effects_allowed_binary`. (arithmetic_side_effects_allowed: rustc_data_structures::fx::FxHashSet<String> = <_>::default()), /// Lint: ARITHMETIC_SIDE_EFFECTS. /// @@ -252,7 +253,7 @@ define_Conf! { /// /// Suppress lints whenever the suggested change would cause breakage for other crates. (avoid_breaking_exported_api: bool = true), - /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION. + /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN. /// /// The minimum rust version that the project supports (msrv: Option<String> = None), @@ -322,7 +323,7 @@ define_Conf! { /// /// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference. (trivial_copy_size_limit: Option<u64> = None), - /// Lint: LARGE_TYPE_PASS_BY_MOVE. + /// Lint: LARGE_TYPES_PASSED_BY_VALUE. /// /// The minimum size (in bytes) to consider a type for passing by reference instead of by value. (pass_by_value_size_limit: u64 = 256), @@ -410,7 +411,7 @@ define_Conf! { /// the slice pattern that is suggested. If more elements would be necessary, the lint is suppressed. /// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements. (max_suggested_slice_pattern_length: u64 = 3), - /// Lint: AWAIT_HOLDING_INVALID_TYPE + /// Lint: AWAIT_HOLDING_INVALID_TYPE. (await_holding_invalid_types: Vec<crate::utils::conf::DisallowedPath> = Vec::new()), /// Lint: LARGE_INCLUDE_FILE. /// @@ -418,25 +419,25 @@ define_Conf! { (max_include_file_size: u64 = 1_000_000), /// Lint: EXPECT_USED. /// - /// Whether `expect` should be allowed within `#[cfg(test)]` + /// Whether `expect` should be allowed in test functions or `#[cfg(test)]` (allow_expect_in_tests: bool = false), /// Lint: UNWRAP_USED. /// - /// Whether `unwrap` should be allowed in test cfg + /// Whether `unwrap` should be allowed in test functions or `#[cfg(test)]` (allow_unwrap_in_tests: bool = false), /// Lint: DBG_MACRO. /// - /// Whether `dbg!` should be allowed in test functions + /// Whether `dbg!` should be allowed in test functions or `#[cfg(test)]` (allow_dbg_in_tests: bool = false), /// Lint: PRINT_STDOUT, PRINT_STDERR. /// - /// Whether print macros (ex. `println!`) should be allowed in test functions + /// Whether print macros (ex. `println!`) should be allowed in test functions or `#[cfg(test)]` (allow_print_in_tests: bool = false), /// Lint: RESULT_LARGE_ERR. /// /// The maximum size of the `Err`-variant in a `Result` returned from a function (large_error_threshold: u64 = 128), - /// Lint: MUTABLE_KEY. + /// Lint: MUTABLE_KEY_TYPE. /// /// A list of paths to types that should be treated like `Arc`, i.e. ignored but /// for the generic parameters for determining interior mutability @@ -445,7 +446,7 @@ define_Conf! { /// /// Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)` (allow_mixed_uninlined_format_args: bool = true), - /// Lint: INDEXING_SLICING + /// Lint: INDEXING_SLICING. /// /// Whether to suppress a restriction lint in constant code. In same /// cases the restructured operation might not be unavoidable, as the @@ -453,6 +454,11 @@ define_Conf! { /// configuration will cause restriction lints to trigger even /// if no suggestion can be made. (suppress_restriction_lint_in_const: bool = false), + /// Lint: MISSING_DOCS_IN_PRIVATE_ITEMS. + /// + /// Whether to **only** check for missing documentation in items visible within the current + /// crate. For example, `pub(crate)` items. + (missing_docs_in_crate_items: bool = false), } /// Search for the configuration file. diff --git a/src/tools/clippy/clippy_lints/src/utils/dump_hir.rs b/src/tools/clippy/clippy_lints/src/utils/dump_hir.rs index 01efc527a..092041aec 100644 --- a/src/tools/clippy/clippy_lints/src/utils/dump_hir.rs +++ b/src/tools/clippy/clippy_lints/src/utils/dump_hir.rs @@ -1,4 +1,5 @@ use clippy_utils::get_attr; +use hir::TraitItem; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -47,6 +48,18 @@ impl<'tcx> LateLintPass<'tcx> for DumpHir { println!("{stmt:#?}"); } } + + fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) { + if has_attr(cx, item.hir_id()) { + println!("{item:#?}"); + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &hir::ImplItem<'_>) { + if has_attr(cx, item.hir_id()) { + println!("{item:#?}"); + } + } } fn has_attr(cx: &LateContext<'_>, hir_id: hir::HirId) -> bool { diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs index 4b33d492a..688a8b865 100644 --- a/src/tools/clippy/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs +++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs @@ -78,7 +78,7 @@ impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol { for item in cx.tcx.module_children(def_id).iter() { if_chain! { if let Res::Def(DefKind::Const, item_def_id) = item.res; - let ty = cx.tcx.type_of(item_def_id); + let ty = cx.tcx.type_of(item_def_id).subst_identity(); if match_type(cx, ty, &paths::SYMBOL); if let Ok(ConstValue::Scalar(value)) = cx.tcx.const_eval_poly(item_def_id); if let Ok(value) = value.to_u32(); diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs index 4c3b1b131..f71820765 100644 --- a/src/tools/clippy/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs +++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs @@ -215,14 +215,13 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { cx, }; let body_id = cx.tcx.hir().body_owned_by( - cx.tcx.hir().local_def_id( - impl_item_refs - .iter() - .find(|iiref| iiref.ident.as_str() == "get_lints") - .expect("LintPass needs to implement get_lints") - .id - .hir_id(), - ), + impl_item_refs + .iter() + .find(|iiref| iiref.ident.as_str() == "get_lints") + .expect("LintPass needs to implement get_lints") + .id + .owner_id + .def_id, ); collector.visit_expr(cx.tcx.hir().body(body_id).value); } diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs index c4d8c28f0..b1b5164ff 100644 --- a/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -14,6 +14,7 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::ty::{match_type, walk_ptrs_ty_depth}; use clippy_utils::{last_path_segment, match_def_path, match_function_call, match_path, paths}; use if_chain::if_chain; +use itertools::Itertools; use rustc_ast as ast; use rustc_data_structures::fx::FxHashMap; use rustc_hir::{ @@ -34,8 +35,10 @@ use std::path::Path; use std::path::PathBuf; use std::process::Command; -/// This is the output file of the lint collector. -const OUTPUT_FILE: &str = "../util/gh-pages/lints.json"; +/// This is the json output file of the lint collector. +const JSON_OUTPUT_FILE: &str = "../util/gh-pages/lints.json"; +/// This is the markdown output file of the lint collector. +const MARKDOWN_OUTPUT_FILE: &str = "../book/src/lint_configuration.md"; /// These lints are excluded from the export. const BLACK_LISTED_LINTS: &[&str] = &["lint_author", "dump_hir", "internal_metadata_collector"]; /// These groups will be ignored by the lint group matcher. This is useful for collections like @@ -176,6 +179,23 @@ This lint has the following configuration variables: ) }) } + + fn configs_to_markdown(&self, map_fn: fn(&ClippyConfiguration) -> String) -> String { + self.config + .iter() + .filter(|config| config.deprecation_reason.is_none()) + .filter(|config| !config.lints.is_empty()) + .map(map_fn) + .join("\n") + } + + fn get_markdown_docs(&self) -> String { + format!( + "## Lint Configuration Options\n| <div style=\"width:290px\">Option</div> | Default Value |\n|--|--|\n{}\n\n{}\n", + self.configs_to_markdown(ClippyConfiguration::to_markdown_table_entry), + self.configs_to_markdown(ClippyConfiguration::to_markdown_paragraph), + ) + } } impl Drop for MetadataCollector { @@ -199,12 +219,37 @@ impl Drop for MetadataCollector { collect_renames(&mut lints); - // Outputting - if Path::new(OUTPUT_FILE).exists() { - fs::remove_file(OUTPUT_FILE).unwrap(); + // Outputting json + if Path::new(JSON_OUTPUT_FILE).exists() { + fs::remove_file(JSON_OUTPUT_FILE).unwrap(); } - let mut file = OpenOptions::new().write(true).create(true).open(OUTPUT_FILE).unwrap(); + let mut file = OpenOptions::new() + .write(true) + .create(true) + .open(JSON_OUTPUT_FILE) + .unwrap(); writeln!(file, "{}", serde_json::to_string_pretty(&lints).unwrap()).unwrap(); + + // Outputting markdown + if Path::new(MARKDOWN_OUTPUT_FILE).exists() { + fs::remove_file(MARKDOWN_OUTPUT_FILE).unwrap(); + } + let mut file = OpenOptions::new() + .write(true) + .create(true) + .open(MARKDOWN_OUTPUT_FILE) + .unwrap(); + writeln!( + file, + "<!-- +This file is generated by `cargo collect-metadata`. +Please use that command to update the file and do not edit it by hand. +--> + +{}", + self.get_markdown_docs(), + ) + .unwrap(); } } @@ -505,6 +550,28 @@ impl ClippyConfiguration { deprecation_reason, } } + + fn to_markdown_paragraph(&self) -> String { + format!( + "### {}\n{}\n\n**Default Value:** `{}` (`{}`)\n\n{}\n\n", + self.name, + self.doc + .lines() + .map(|line| line.strip_prefix(" ").unwrap_or(line)) + .join("\n"), + self.default, + self.config_type, + self.lints + .iter() + .map(|name| name.to_string().split_whitespace().next().unwrap().to_string()) + .map(|name| format!("* [{name}](https://rust-lang.github.io/rust-clippy/master/index.html#{name})")) + .join("\n"), + ) + } + + fn to_markdown_table_entry(&self) -> String { + format!("| [{}](#{}) | `{}` |", self.name, self.name, self.default) + } } fn collect_configs() -> Vec<ClippyConfiguration> { diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs index 9876a8a76..09f0f0d0a 100644 --- a/src/tools/clippy/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs +++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs @@ -39,6 +39,7 @@ impl LateLintPass<'_> for MsrvAttrImpl { if self_ty_def.all_fields().any(|f| { cx.tcx .type_of(f.did) + .subst_identity() .walk() .filter(|t| matches!(t.unpack(), GenericArgKind::Type(_))) .any(|t| match_type(cx, t.expect_ty(), &paths::MSRV)) diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs index 714436363..b59ef4086 100644 --- a/src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs +++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs @@ -3,7 +3,7 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::{def_path_def_ids, is_lint_allowed, match_any_def_paths, peel_hir_expr_refs}; use if_chain::if_chain; use rustc_ast::ast::LitKind; -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; @@ -44,7 +44,7 @@ impl_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]); #[derive(Default)] pub struct UnnecessaryDefPath { - array_def_ids: FxHashSet<(DefId, Span)>, + array_def_ids: FxIndexSet<(DefId, Span)>, linted_def_ids: FxHashSet<DefId>, } @@ -229,11 +229,11 @@ fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Ve Res::Def(DefKind::Static(_), def_id) => read_mir_alloc_def_path( cx, cx.tcx.eval_static_initializer(def_id).ok()?.inner(), - cx.tcx.type_of(def_id), + cx.tcx.type_of(def_id).subst_identity(), ), Res::Def(DefKind::Const, def_id) => match cx.tcx.const_eval_poly(def_id).ok()? { ConstValue::ByRef { alloc, offset } if offset.bytes() == 0 => { - read_mir_alloc_def_path(cx, alloc.inner(), cx.tcx.type_of(def_id)) + read_mir_alloc_def_path(cx, alloc.inner(), cx.tcx.type_of(def_id).subst_identity()) }, _ => None, }, diff --git a/src/tools/clippy/clippy_lints/src/zero_sized_map_values.rs b/src/tools/clippy/clippy_lints/src/zero_sized_map_values.rs index 6cf2a955f..93e4b023c 100644 --- a/src/tools/clippy/clippy_lints/src/zero_sized_map_values.rs +++ b/src/tools/clippy/clippy_lints/src/zero_sized_map_values.rs @@ -5,7 +5,7 @@ use rustc_hir::{self as hir, HirId, ItemKind, Node}; use rustc_hir_analysis::hir_ty_to_ty; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::layout::LayoutOf as _; -use rustc_middle::ty::{Adt, Ty, TypeVisitable}; +use rustc_middle::ty::{Adt, Ty, TypeVisitableExt}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::sym; diff --git a/src/tools/clippy/clippy_utils/Cargo.toml b/src/tools/clippy/clippy_utils/Cargo.toml index ac6a566b9..173469f6c 100644 --- a/src/tools/clippy/clippy_utils/Cargo.toml +++ b/src/tools/clippy/clippy_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_utils" -version = "0.1.68" +version = "0.1.69" edition = "2021" publish = false diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs index 9d0263e93..d82098523 100644 --- a/src/tools/clippy/clippy_utils/src/ast_utils.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs @@ -144,7 +144,8 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool { (_, Paren(r)) => eq_expr(l, r), (Err, Err) => true, (Box(l), Box(r)) | (Try(l), Try(r)) | (Await(l), Await(r)) => eq_expr(l, r), - (Array(l), Array(r)) | (Tup(l), Tup(r)) => over(l, r, |l, r| eq_expr(l, r)), + (Array(l), Array(r)) => over(l, r, |l, r| eq_expr(l, r)), + (Tup(l), Tup(r)) => over(l, r, |l, r| eq_expr(l, r)), (Repeat(le, ls), Repeat(re, rs)) => eq_expr(le, re) && eq_expr(&ls.value, &rs.value), (Call(lc, la), Call(rc, ra)) => eq_expr(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)), ( diff --git a/src/tools/clippy/clippy_utils/src/consts.rs b/src/tools/clippy/clippy_utils/src/consts.rs index a67bd8d46..bb8890dca 100644 --- a/src/tools/clippy/clippy_utils/src/consts.rs +++ b/src/tools/clippy/clippy_utils/src/consts.rs @@ -237,7 +237,7 @@ pub fn constant<'tcx>( typeck_results, param_env: lcx.param_env, needed_resolution: false, - substs: lcx.tcx.intern_substs(&[]), + substs: ty::List::empty(), }; cx.expr(e).map(|cst| (cst, cx.needed_resolution)) } @@ -306,7 +306,7 @@ pub fn constant_context<'a, 'tcx>( typeck_results, param_env: lcx.param_env, needed_resolution: false, - substs: lcx.tcx.intern_substs(&[]), + substs: ty::List::empty(), } } @@ -335,7 +335,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { ExprKind::Tup(tup) => self.multi(tup).map(Constant::Tuple), ExprKind::Repeat(value, _) => { let n = match self.typeck_results.expr_ty(e).kind() { - ty::Array(_, n) => n.try_eval_usize(self.lcx.tcx, self.lcx.param_env)?, + ty::Array(_, n) => n.try_eval_target_usize(self.lcx.tcx, self.lcx.param_env)?, _ => span_bug!(e.span, "typeck error"), }; self.expr(value).map(|v| Constant::Repeat(Box::new(v), n)) @@ -640,7 +640,7 @@ pub fn miri_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::ConstantKind<'tcx>) - }, mir::ConstantKind::Val(ConstValue::ByRef { alloc, offset: _ }, _) => match result.ty().kind() { ty::Array(sub_type, len) => match sub_type.kind() { - ty::Float(FloatTy::F32) => match len.kind().try_to_machine_usize(tcx) { + ty::Float(FloatTy::F32) => match len.kind().try_to_target_usize(tcx) { Some(len) => alloc .inner() .inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * usize::try_from(len).unwrap())) @@ -651,7 +651,7 @@ pub fn miri_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::ConstantKind<'tcx>) - .map(Constant::Vec), _ => None, }, - ty::Float(FloatTy::F64) => match len.kind().try_to_machine_usize(tcx) { + ty::Float(FloatTy::F64) => match len.kind().try_to_target_usize(tcx) { Some(len) => alloc .inner() .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * usize::try_from(len).unwrap())) diff --git a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs index 967119369..ee2f816f1 100644 --- a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs +++ b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs @@ -50,7 +50,7 @@ fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg: let name = name.as_str(); let ty = match cx.tcx.impl_of_method(fn_id) { - Some(id) => cx.tcx.type_of(id), + Some(id) => cx.tcx.type_of(id).subst_identity(), None => return Lazy, }; @@ -71,7 +71,7 @@ fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg: .variants() .iter() .flat_map(|v| v.fields.iter()) - .any(|x| matches!(cx.tcx.type_of(x.did).peel_refs().kind(), ty::Param(_))) + .any(|x| matches!(cx.tcx.type_of(x.did).subst_identity().peel_refs().kind(), ty::Param(_))) && all_predicates_of(cx.tcx, fn_id).all(|(pred, _)| match pred.kind().skip_binder() { PredicateKind::Clause(ty::Clause::Trait(pred)) => cx.tcx.trait_def(pred.trait_ref.def_id).is_marker, _ => true, @@ -79,7 +79,7 @@ fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg: && subs.types().all(|x| matches!(x.peel_refs().kind(), ty::Param(_))) { // Limit the function to either `(self) -> bool` or `(&self) -> bool` - match &**cx.tcx.fn_sig(fn_id).skip_binder().inputs_and_output { + match &**cx.tcx.fn_sig(fn_id).subst_identity().skip_binder().inputs_and_output { [arg, res] if !arg.is_mutable_ptr() && arg.peel_refs() == ty && res.is_bool() => NoChange, _ => Lazy, } @@ -193,7 +193,7 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS | ExprKind::Ret(_) | ExprKind::InlineAsm(_) | ExprKind::Yield(..) - | ExprKind::Err => { + | ExprKind::Err(_) => { self.eagerness = ForceNoChange; return; }, diff --git a/src/tools/clippy/clippy_utils/src/higher.rs b/src/tools/clippy/clippy_utils/src/higher.rs index 4604ae5c2..50bef3709 100644 --- a/src/tools/clippy/clippy_utils/src/higher.rs +++ b/src/tools/clippy/clippy_utils/src/higher.rs @@ -287,15 +287,12 @@ impl<'a> VecArgs<'a> { Some(VecArgs::Repeat(&args[0], &args[1])) } else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 { // `vec![a, b, c]` case - if_chain! { - if let hir::ExprKind::Box(boxed) = args[0].kind; - if let hir::ExprKind::Array(args) = boxed.kind; - then { - return Some(VecArgs::Vec(args)); - } + if let hir::ExprKind::Call(_, [arg]) = &args[0].kind + && let hir::ExprKind::Array(args) = arg.kind { + Some(VecArgs::Vec(args)) + } else { + None } - - None } else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() { Some(VecArgs::Vec(&[])) } else { diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index 2bbe1a19b..0603755f8 100644 --- a/src/tools/clippy/clippy_utils/src/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -714,7 +714,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { } self.hash_pat(pat); }, - ExprKind::Err => {}, + ExprKind::Err(_) => {}, ExprKind::Lit(ref l) => { l.node.hash(&mut self.s); }, @@ -986,7 +986,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { TyKind::Typeof(anon_const) => { self.hash_body(anon_const.body); }, - TyKind::Err | TyKind::Infer | TyKind::Never => {}, + TyKind::Err(_) | TyKind::Infer | TyKind::Never => {}, } } diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index 7a4a9036d..f02f8ecb4 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -1,6 +1,5 @@ #![feature(array_chunks)] #![feature(box_patterns)] -#![feature(control_flow_enum)] #![feature(let_chains)] #![feature(lint_reasons)] #![feature(never_type)] @@ -105,7 +104,7 @@ use rustc_middle::ty::fast_reject::SimplifiedType::{ PtrSimplifiedType, SliceSimplifiedType, StrSimplifiedType, UintSimplifiedType, }; use rustc_middle::ty::{ - layout::IntegerExt, BorrowKind, ClosureKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeVisitable, UpvarCapture, + layout::IntegerExt, BorrowKind, ClosureKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeVisitableExt, UpvarCapture, }; use rustc_middle::ty::{FloatTy, IntTy, UintTy}; use rustc_span::hygiene::{ExpnKind, MacroKind}; @@ -318,7 +317,7 @@ pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) /// Checks if a method is defined in an impl of a diagnostic item pub fn is_diag_item_method(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool { if let Some(impl_did) = cx.tcx.impl_of_method(def_id) { - if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() { + if let Some(adt) = cx.tcx.type_of(impl_did).subst_identity().ty_adt_def() { return cx.tcx.is_diagnostic_item(diag_item, adt.did()); } } @@ -553,7 +552,7 @@ fn non_local_item_children_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) .filter(|item| item.ident.name == name) .map(|child| child.res.expect_non_local()) .collect(), - DefKind::Impl => tcx + DefKind::Impl { .. } => tcx .associated_item_def_ids(def_id) .iter() .copied() @@ -813,7 +812,7 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath< if let QPath::TypeRelative(_, method) = path { if method.ident.name == sym::new { if let Some(impl_did) = cx.tcx.impl_of_method(def_id) { - if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() { + if let Some(adt) = cx.tcx.type_of(impl_did).subst_identity().ty_adt_def() { return std_types_symbols.iter().any(|&symbol| { cx.tcx.is_diagnostic_item(symbol, adt.did()) || Some(adt.did()) == cx.tcx.lang_items().string() }); @@ -1119,9 +1118,8 @@ pub fn can_move_expr_to_closure<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<' self.captures.entry(l).and_modify(|e| *e |= cap).or_insert(cap); } }, - ExprKind::Closure { .. } => { - let closure_id = self.cx.tcx.hir().local_def_id(e.hir_id); - for capture in self.cx.typeck_results().closure_min_captures_flattened(closure_id) { + ExprKind::Closure(closure) => { + for capture in self.cx.typeck_results().closure_min_captures_flattened(closure.def_id) { let local_id = match capture.place.base { PlaceBase::Local(id) => id, PlaceBase::Upvar(var) => var.var_path.hir_id, @@ -1379,7 +1377,7 @@ pub fn get_enclosing_loop_or_multi_call_closure<'tcx>( .chain(args.iter()) .position(|arg| arg.hir_id == id)?; let id = cx.typeck_results().type_dependent_def_id(e.hir_id)?; - let ty = cx.tcx.fn_sig(id).skip_binder().inputs()[i]; + let ty = cx.tcx.fn_sig(id).subst_identity().skip_binder().inputs()[i]; ty_is_fn_once_param(cx.tcx, ty, cx.tcx.param_env(id).caller_bounds()).then_some(()) }, _ => None, @@ -1578,16 +1576,14 @@ pub fn is_direct_expn_of(span: Span, name: &str) -> Option<Span> { } /// Convenience function to get the return type of a function. -pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_item: hir::HirId) -> Ty<'tcx> { - let fn_def_id = cx.tcx.hir().local_def_id(fn_item); - let ret_ty = cx.tcx.fn_sig(fn_def_id).output(); +pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_def_id: hir::OwnerId) -> Ty<'tcx> { + let ret_ty = cx.tcx.fn_sig(fn_def_id).subst_identity().output(); cx.tcx.erase_late_bound_regions(ret_ty) } /// Convenience function to get the nth argument type of a function. -pub fn nth_arg<'tcx>(cx: &LateContext<'tcx>, fn_item: hir::HirId, nth: usize) -> Ty<'tcx> { - let fn_def_id = cx.tcx.hir().local_def_id(fn_item); - let arg = cx.tcx.fn_sig(fn_def_id).input(nth); +pub fn nth_arg<'tcx>(cx: &LateContext<'tcx>, fn_def_id: hir::OwnerId, nth: usize) -> Ty<'tcx> { + let arg = cx.tcx.fn_sig(fn_def_id).subst_identity().input(nth); cx.tcx.erase_late_bound_regions(arg) } @@ -2491,6 +2487,10 @@ pub fn span_extract_comment(sm: &SourceMap, span: Span) -> String { comments_buf.join("\n") } +pub fn span_find_starting_semi(sm: &SourceMap, span: Span) -> Span { + sm.span_take_while(span, |&ch| ch == ' ' || ch == ';') +} + macro_rules! op_utils { ($($name:ident $assign:ident)*) => { /// Binary operation traits like `LangItem::Add` diff --git a/src/tools/clippy/clippy_utils/src/macros.rs b/src/tools/clippy/clippy_utils/src/macros.rs index 77c5f1155..be6133d32 100644 --- a/src/tools/clippy/clippy_utils/src/macros.rs +++ b/src/tools/clippy/clippy_utils/src/macros.rs @@ -1,6 +1,5 @@ #![allow(clippy::similar_names)] // `expr` and `expn` -use crate::is_path_diagnostic_item; use crate::source::snippet_opt; use crate::visitors::{for_each_expr, Descend}; @@ -8,7 +7,7 @@ use arrayvec::ArrayVec; use itertools::{izip, Either, Itertools}; use rustc_ast::ast::LitKind; use rustc_hir::intravisit::{walk_expr, Visitor}; -use rustc_hir::{self as hir, Expr, ExprField, ExprKind, HirId, Node, QPath}; +use rustc_hir::{self as hir, Expr, ExprField, ExprKind, HirId, LangItem, Node, QPath, TyKind}; use rustc_lexer::unescape::unescape_literal; use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind}; use rustc_lint::LateContext; @@ -328,7 +327,7 @@ fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> } else { match cx.tcx.item_name(macro_call.def_id) { // `cfg!(debug_assertions)` in `debug_assert!` - sym::cfg => ControlFlow::CONTINUE, + sym::cfg => ControlFlow::Continue(()), // assert!(other_macro!(..)) _ => ControlFlow::Break(true), } @@ -392,11 +391,18 @@ impl FormatString { }; let mut unescaped = String::with_capacity(inner.len()); + // Sometimes the original string comes from a macro which accepts a malformed string, such as in a + // #[display(""somestring)] attribute (accepted by the `displaythis` crate). Reconstructing the + // string from the span will not be possible, so we will just return None here. + let mut unparsable = false; unescape_literal(inner, mode, &mut |_, ch| match ch { Ok(ch) => unescaped.push(ch), Err(e) if !e.is_fatal() => (), - Err(e) => panic!("{e:?}"), + Err(_) => unparsable = true, }); + if unparsable { + return None; + } let mut parts = Vec::new(); let _: Option<!> = for_each_expr(pieces, |expr| { @@ -439,8 +445,7 @@ impl<'tcx> FormatArgsValues<'tcx> { // ArgumentV1::from_usize(<val>) if let ExprKind::Call(callee, [val]) = expr.kind && let ExprKind::Path(QPath::TypeRelative(ty, _)) = callee.kind - && let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind - && path.segments.last().unwrap().ident.name == sym::ArgumentV1 + && let TyKind::Path(QPath::LangItem(LangItem::FormatArgument, _, _)) = ty.kind { let val_idx = if val.span.ctxt() == expr.span.ctxt() && let ExprKind::Field(_, field) = val.kind @@ -486,20 +491,6 @@ struct ParamPosition { impl<'tcx> Visitor<'tcx> for ParamPosition { fn visit_expr_field(&mut self, field: &'tcx ExprField<'tcx>) { - fn parse_count(expr: &Expr<'_>) -> Option<usize> { - // ::core::fmt::rt::v1::Count::Param(1usize), - if let ExprKind::Call(ctor, [val]) = expr.kind - && let ExprKind::Path(QPath::Resolved(_, path)) = ctor.kind - && path.segments.last()?.ident.name == sym::Param - && let ExprKind::Lit(lit) = &val.kind - && let LitKind::Int(pos, _) = lit.node - { - Some(pos as usize) - } else { - None - } - } - match field.ident.name { sym::position => { if let ExprKind::Lit(lit) = &field.expr.kind @@ -519,15 +510,41 @@ impl<'tcx> Visitor<'tcx> for ParamPosition { } } +fn parse_count(expr: &Expr<'_>) -> Option<usize> { + // <::core::fmt::rt::v1::Count>::Param(1usize), + if let ExprKind::Call(ctor, [val]) = expr.kind + && let ExprKind::Path(QPath::TypeRelative(_, path)) = ctor.kind + && path.ident.name == sym::Param + && let ExprKind::Lit(lit) = &val.kind + && let LitKind::Int(pos, _) = lit.node + { + Some(pos as usize) + } else { + None + } +} + /// Parses the `fmt` arg of `Arguments::new_v1_formatted(pieces, args, fmt, _)` fn parse_rt_fmt<'tcx>(fmt_arg: &'tcx Expr<'tcx>) -> Option<impl Iterator<Item = ParamPosition> + 'tcx> { if let ExprKind::AddrOf(.., array) = fmt_arg.kind && let ExprKind::Array(specs) = array.kind { Some(specs.iter().map(|spec| { - let mut position = ParamPosition::default(); - position.visit_expr(spec); - position + if let ExprKind::Call(f, args) = spec.kind + && let ExprKind::Path(QPath::TypeRelative(ty, f)) = f.kind + && let TyKind::Path(QPath::LangItem(LangItem::FormatPlaceholder, _, _)) = ty.kind + && f.ident.name == sym::new + && let [position, _fill, _align, _flags, precision, width] = args + && let ExprKind::Lit(position) = &position.kind + && let LitKind::Int(position, _) = position.node { + ParamPosition { + value: position as usize, + width: parse_count(width), + precision: parse_count(precision), + } + } else { + ParamPosition::default() + } })) } else { None @@ -701,8 +718,8 @@ pub struct FormatSpec<'tcx> { pub fill: Option<char>, /// Optionally specified alignment. pub align: Alignment, - /// Packed version of various flags provided, see [`rustc_parse_format::Flag`]. - pub flags: u32, + /// Whether all flag options are set to default (no flags specified). + pub no_flags: bool, /// Represents either the maximum width or the integer precision. pub precision: Count<'tcx>, /// The minimum width, will be padded according to `width`/`align` @@ -718,7 +735,7 @@ impl<'tcx> FormatSpec<'tcx> { Some(Self { fill: spec.fill, align: spec.align, - flags: spec.flags, + no_flags: spec.sign.is_none() && !spec.alternate && !spec.zero_pad && spec.debug_hex.is_none(), precision: Count::new( FormatParamUsage::Precision, spec.precision, @@ -760,10 +777,7 @@ impl<'tcx> FormatSpec<'tcx> { /// Has no other formatting specifiers than setting the format trait. returns true for `{}`, /// `{foo}`, `{:?}`, but false for `{foo:5}`, `{3:.5?}` pub fn is_default_for_trait(&self) -> bool { - self.width.is_implied() - && self.precision.is_implied() - && self.align == Alignment::AlignUnknown - && self.flags == 0 + self.width.is_implied() && self.precision.is_implied() && self.align == Alignment::AlignUnknown && self.no_flags } } @@ -890,7 +904,7 @@ impl<'tcx> FormatArgsExpn<'tcx> { // ::core::fmt::Arguments::new_v1_formatted(pieces, args, fmt, _unsafe_arg) if let ExprKind::Call(callee, [pieces, args, rest @ ..]) = expr.kind && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind - && is_path_diagnostic_item(cx, ty, sym::Arguments) + && let TyKind::Path(QPath::LangItem(LangItem::FormatArguments, _, _)) = ty.kind && matches!(seg.ident.as_str(), "new_v1" | "new_v1_formatted") { let format_string = FormatString::new(cx, pieces)?; diff --git a/src/tools/clippy/clippy_utils/src/mir/possible_borrower.rs b/src/tools/clippy/clippy_utils/src/mir/possible_borrower.rs index 9adae7733..920ce8e65 100644 --- a/src/tools/clippy/clippy_utils/src/mir/possible_borrower.rs +++ b/src/tools/clippy/clippy_utils/src/mir/possible_borrower.rs @@ -4,7 +4,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_index::bit_set::{BitSet, HybridBitSet}; use rustc_lint::LateContext; use rustc_middle::mir::{self, visit::Visitor as _, Mutability}; -use rustc_middle::ty::{self, visit::TypeVisitor}; +use rustc_middle::ty::{self, visit::TypeVisitor, TyCtxt}; use rustc_mir_dataflow::{impls::MaybeStorageLive, Analysis, ResultsCursor}; use std::borrow::Cow; use std::ops::ControlFlow; @@ -136,11 +136,11 @@ impl<'a, 'b, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'b, struct ContainsRegion; -impl TypeVisitor<'_> for ContainsRegion { +impl TypeVisitor<TyCtxt<'_>> for ContainsRegion { type BreakTy = (); fn visit_region(&mut self, _: ty::Region<'_>) -> ControlFlow<Self::BreakTy> { - ControlFlow::BREAK + ControlFlow::Break(()) } } diff --git a/src/tools/clippy/clippy_utils/src/numeric_literal.rs b/src/tools/clippy/clippy_utils/src/numeric_literal.rs index 42bdfd482..c225398ad 100644 --- a/src/tools/clippy/clippy_utils/src/numeric_literal.rs +++ b/src/tools/clippy/clippy_utils/src/numeric_literal.rs @@ -186,7 +186,7 @@ impl<'a> NumericLiteral<'a> { // The exponent may have a sign, output it early, otherwise it will be // treated as a digit if digits.clone().next() == Some('-') { - let _ = digits.next(); + let _: Option<char> = digits.next(); output.push('-'); } diff --git a/src/tools/clippy/clippy_utils/src/paths.rs b/src/tools/clippy/clippy_utils/src/paths.rs index 95eebab75..4aae0f728 100644 --- a/src/tools/clippy/clippy_utils/src/paths.rs +++ b/src/tools/clippy/clippy_utils/src/paths.rs @@ -115,6 +115,7 @@ pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"]; pub const STD_IO_SEEK: [&str; 3] = ["std", "io", "Seek"]; pub const STD_IO_SEEK_FROM_CURRENT: [&str; 4] = ["std", "io", "SeekFrom", "Current"]; pub const STD_IO_SEEKFROM_START: [&str; 4] = ["std", "io", "SeekFrom", "Start"]; +pub const STD_PROCESS_COMMAND: [&str; 3] = ["std", "process", "Command"]; pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_str"]; pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"]; pub const STRING_NEW: [&str; 4] = ["alloc", "string", "String", "new"]; diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index e5d7da682..1a35fe050 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -30,12 +30,14 @@ pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv) ty::Clause::RegionOutlives(_) | ty::Clause::TypeOutlives(_) | ty::Clause::Projection(_) - | ty::Clause::Trait(..), + | ty::Clause::Trait(..) + | ty::Clause::ConstArgHasType(..), ) | ty::PredicateKind::WellFormed(_) | ty::PredicateKind::ConstEvaluatable(..) | ty::PredicateKind::ConstEquate(..) | ty::PredicateKind::TypeWellFormedFromEnv(..) => continue, + ty::PredicateKind::AliasEq(..) => panic!("alias eq predicate on function: {predicate:#?}"), ty::PredicateKind::ObjectSafe(_) => panic!("object safe predicate on function: {predicate:#?}"), ty::PredicateKind::ClosureKind(..) => panic!("closure kind predicate on function: {predicate:#?}"), ty::PredicateKind::Subtype(_) => panic!("subtype predicate on function: {predicate:#?}"), @@ -55,7 +57,7 @@ pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv) // impl trait is gone in MIR, so check the return type manually check_ty( tcx, - tcx.fn_sig(def_id).output().skip_binder(), + tcx.fn_sig(def_id).subst_identity().output().skip_binder(), body.local_decls.iter().next().unwrap().source_info.span, )?; @@ -240,6 +242,7 @@ fn check_statement<'tcx>( | StatementKind::Retag { .. } | StatementKind::AscribeUserType(..) | StatementKind::Coverage(..) + | StatementKind::ConstEvalCounter | StatementKind::Nop => Ok(()), } } diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs index e7879bb19..07feadca2 100644 --- a/src/tools/clippy/clippy_utils/src/sugg.rs +++ b/src/tools/clippy/clippy_utils/src/sugg.rs @@ -20,7 +20,7 @@ use rustc_middle::mir::{FakeReadCause, Mutability}; use rustc_middle::ty; use rustc_span::source_map::{BytePos, CharPos, Pos, Span, SyntaxContext}; use std::borrow::Cow; -use std::fmt::{Display, Write as _}; +use std::fmt::{self, Display, Write as _}; use std::ops::{Add, Neg, Not, Sub}; /// A helper type to build suggestion correctly handling parentheses. @@ -157,7 +157,7 @@ impl<'a> Sugg<'a> { | hir::ExprKind::Ret(..) | hir::ExprKind::Struct(..) | hir::ExprKind::Tup(..) - | hir::ExprKind::Err => Sugg::NonParen(get_snippet(expr.span)), + | hir::ExprKind::Err(_) => Sugg::NonParen(get_snippet(expr.span)), hir::ExprKind::DropTemps(inner) => Self::hir_from_snippet(inner, get_snippet), hir::ExprKind::Assign(lhs, rhs, _) => { Sugg::BinOp(AssocOp::Assign, get_snippet(lhs.span), get_snippet(rhs.span)) @@ -219,6 +219,7 @@ impl<'a> Sugg<'a> { | ast::ExprKind::Repeat(..) | ast::ExprKind::Ret(..) | ast::ExprKind::Yeet(..) + | ast::ExprKind::FormatArgs(..) | ast::ExprKind::Struct(..) | ast::ExprKind::Try(..) | ast::ExprKind::TryBlock(..) @@ -808,7 +809,10 @@ pub struct DerefClosure { /// /// note: this only works on single line immutable closures with exactly one input parameter. pub fn deref_closure_args(cx: &LateContext<'_>, closure: &hir::Expr<'_>) -> Option<DerefClosure> { - if let hir::ExprKind::Closure(&Closure { fn_decl, body, .. }) = closure.kind { + if let hir::ExprKind::Closure(&Closure { + fn_decl, def_id, body, .. + }) = closure.kind + { let closure_body = cx.tcx.hir().body(body); // is closure arg a type annotated double reference (i.e.: `|x: &&i32| ...`) // a type annotation is present if param `kind` is different from `TyKind::Infer` @@ -828,10 +832,8 @@ pub fn deref_closure_args(cx: &LateContext<'_>, closure: &hir::Expr<'_>) -> Opti applicability: Applicability::MachineApplicable, }; - let fn_def_id = cx.tcx.hir().local_def_id(closure.hir_id); let infcx = cx.tcx.infer_ctxt().build(); - ExprUseVisitor::new(&mut visitor, &infcx, fn_def_id, cx.param_env, cx.typeck_results()) - .consume_body(closure_body); + ExprUseVisitor::new(&mut visitor, &infcx, def_id, cx.param_env, cx.typeck_results()).consume_body(closure_body); if !visitor.suggestion_start.is_empty() { return Some(DerefClosure { @@ -884,7 +886,7 @@ impl<'tcx> DerefDelegate<'_, 'tcx> { .cx .typeck_results() .type_dependent_def_id(parent_expr.hir_id) - .map(|did| self.cx.tcx.fn_sig(did).skip_binder()) + .map(|did| self.cx.tcx.fn_sig(did).subst_identity().skip_binder()) { std::iter::once(receiver) .chain(call_args.iter()) @@ -930,7 +932,7 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> { if cmt.place.projections.is_empty() { // handle item without any projection, that needs an explicit borrowing // i.e.: suggest `&x` instead of `x` - let _ = write!(self.suggestion_start, "{start_snip}&{ident_str}"); + let _: fmt::Result = write!(self.suggestion_start, "{start_snip}&{ident_str}"); } else { // cases where a parent `Call` or `MethodCall` is using the item // i.e.: suggest `.contains(&x)` for `.find(|x| [1, 2, 3].contains(x)).is_none()` @@ -945,7 +947,7 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> { // given expression is the self argument and will be handled completely by the compiler // i.e.: `|x| x.is_something()` ExprKind::MethodCall(_, self_expr, ..) if self_expr.hir_id == cmt.hir_id => { - let _ = write!(self.suggestion_start, "{start_snip}{ident_str_with_proj}"); + let _: fmt::Result = write!(self.suggestion_start, "{start_snip}{ident_str_with_proj}"); self.next_pos = span.hi(); return; }, @@ -1053,7 +1055,7 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> { } } - let _ = write!(self.suggestion_start, "{start_snip}{replacement_str}"); + let _: fmt::Result = write!(self.suggestion_start, "{start_snip}{replacement_str}"); } self.next_pos = span.hi(); } diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs index 99fba4fe7..25654e695 100644 --- a/src/tools/clippy/clippy_utils/src/ty.rs +++ b/src/tools/clippy/clippy_utils/src/ty.rs @@ -17,8 +17,8 @@ use rustc_lint::LateContext; use rustc_middle::mir::interpret::{ConstValue, Scalar}; use rustc_middle::ty::{ self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, DefIdTree, FnSig, IntTy, List, ParamEnv, Predicate, - PredicateKind, Region, RegionKind, SubstsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor, UintTy, - VariantDef, VariantDiscr, + PredicateKind, Region, RegionKind, SubstsRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, + TypeVisitor, UintTy, VariantDef, VariantDiscr, }; use rustc_middle::ty::{GenericArg, GenericArgKind}; use rustc_span::symbol::Ident; @@ -237,7 +237,7 @@ pub fn implements_trait_with_env<'tcx>( kind: TypeVariableOriginKind::MiscVariable, span: DUMMY_SP, }; - let ty_params = tcx.mk_substs( + let ty_params = tcx.mk_substs_from_iter( ty_params .into_iter() .map(|arg| arg.unwrap_or_else(|| infcx.next_ty_var(orig).into())), @@ -346,7 +346,7 @@ pub fn is_non_aggregate_primitive_type(ty: Ty<'_>) -> bool { pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool { match *ty.kind() { ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true, - ty::Ref(_, inner, _) if *inner.kind() == ty::Str => true, + ty::Ref(_, inner, _) if inner.is_str() => true, ty::Array(inner_type, _) | ty::Slice(inner_type) => is_recursively_primitive_type(inner_type), ty::Tuple(inner_types) => inner_types.iter().all(is_recursively_primitive_type), _ => false, @@ -628,7 +628,7 @@ impl<'tcx> ExprFnSig<'tcx> { /// If the expression is function like, get the signature for it. pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<ExprFnSig<'tcx>> { if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) = path_res(cx, expr) { - Some(ExprFnSig::Sig(cx.tcx.fn_sig(id), Some(id))) + Some(ExprFnSig::Sig(cx.tcx.fn_sig(id).subst_identity(), Some(id))) } else { ty_sig(cx, cx.typeck_results().expr_ty_adjusted(expr).peel_refs()) } @@ -646,10 +646,13 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'t .and_then(|id| cx.tcx.hir().fn_decl_by_hir_id(cx.tcx.hir().local_def_id_to_hir_id(id))); Some(ExprFnSig::Closure(decl, subs.as_closure().sig())) }, - ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.bound_fn_sig(id).subst(cx.tcx, subs), Some(id))), - ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => { - sig_from_bounds(cx, ty, cx.tcx.item_bounds(def_id).subst(cx.tcx, substs), cx.tcx.opt_parent(def_id)) - }, + ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.fn_sig(id).subst(cx.tcx, subs), Some(id))), + ty::Alias(ty::Opaque, ty::AliasTy { def_id, substs, .. }) => sig_from_bounds( + cx, + ty, + cx.tcx.item_bounds(def_id).subst(cx.tcx, substs), + cx.tcx.opt_parent(def_id), + ), ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig, None)), ty::Dynamic(bounds, _, _) => { let lang_items = cx.tcx.lang_items(); @@ -777,7 +780,7 @@ impl core::ops::Add<u32> for EnumValue { #[expect(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] pub fn read_explicit_enum_value(tcx: TyCtxt<'_>, id: DefId) -> Option<EnumValue> { if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) { - match tcx.type_of(id).kind() { + match tcx.type_of(id).subst_identity().kind() { ty::Int(_) => Some(EnumValue::Signed(match value.size().bytes() { 1 => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8), 2 => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16), @@ -835,7 +838,7 @@ pub fn for_each_top_level_late_bound_region<B>( index: u32, f: F, } - impl<'tcx, B, F: FnMut(BoundRegion) -> ControlFlow<B>> TypeVisitor<'tcx> for V<F> { + impl<'tcx, B, F: FnMut(BoundRegion) -> ControlFlow<B>> TypeVisitor<TyCtxt<'tcx>> for V<F> { type BreakTy = B; fn visit_region(&mut self, r: Region<'tcx>) -> ControlFlow<Self::BreakTy> { if let RegionKind::ReLateBound(idx, bound) = r.kind() && idx.as_u32() == self.index { @@ -844,7 +847,7 @@ pub fn for_each_top_level_late_bound_region<B>( ControlFlow::Continue(()) } } - fn visit_binder<T: TypeVisitable<'tcx>>(&mut self, t: &Binder<'tcx, T>) -> ControlFlow<Self::BreakTy> { + fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(&mut self, t: &Binder<'tcx, T>) -> ControlFlow<Self::BreakTy> { self.index += 1; let res = t.super_visit_with(self); self.index -= 1; @@ -891,16 +894,29 @@ impl AdtVariantInfo { } /// Gets the struct or enum variant from the given `Res` -pub fn variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option<&'tcx VariantDef> { +pub fn adt_and_variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option<(AdtDef<'tcx>, &'tcx VariantDef)> { match res { - Res::Def(DefKind::Struct, id) => Some(cx.tcx.adt_def(id).non_enum_variant()), - Res::Def(DefKind::Variant, id) => Some(cx.tcx.adt_def(cx.tcx.parent(id)).variant_with_id(id)), - Res::Def(DefKind::Ctor(CtorOf::Struct, _), id) => Some(cx.tcx.adt_def(cx.tcx.parent(id)).non_enum_variant()), + Res::Def(DefKind::Struct, id) => { + let adt = cx.tcx.adt_def(id); + Some((adt, adt.non_enum_variant())) + }, + Res::Def(DefKind::Variant, id) => { + let adt = cx.tcx.adt_def(cx.tcx.parent(id)); + Some((adt, adt.variant_with_id(id))) + }, + Res::Def(DefKind::Ctor(CtorOf::Struct, _), id) => { + let adt = cx.tcx.adt_def(cx.tcx.parent(id)); + Some((adt, adt.non_enum_variant())) + }, Res::Def(DefKind::Ctor(CtorOf::Variant, _), id) => { let var_id = cx.tcx.parent(id); - Some(cx.tcx.adt_def(cx.tcx.parent(var_id)).variant_with_id(var_id)) + let adt = cx.tcx.adt_def(cx.tcx.parent(var_id)); + Some((adt, adt.variant_with_id(var_id))) + }, + Res::SelfCtor(id) => { + let adt = cx.tcx.type_of(id).subst_identity().ty_adt_def().unwrap(); + Some((adt, adt.non_enum_variant())) }, - Res::SelfCtor(id) => Some(cx.tcx.type_of(id).ty_adt_def().unwrap().non_enum_variant()), _ => None, } } @@ -946,7 +962,7 @@ pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 { (Ok(size), _) => size, (Err(_), ty::Tuple(list)) => list.as_substs().types().map(|t| approx_ty_size(cx, t)).sum(), (Err(_), ty::Array(t, n)) => { - n.try_eval_usize(cx.tcx, cx.param_env).unwrap_or_default() * approx_ty_size(cx, *t) + n.try_eval_target_usize(cx.tcx, cx.param_env).unwrap_or_default() * approx_ty_size(cx, *t) }, (Err(_), ty::Adt(def, subst)) if def.is_struct() => def .variants() @@ -1062,7 +1078,7 @@ pub fn make_projection<'tcx>( tcx, container_id, assoc_ty, - tcx.mk_substs(substs.into_iter().map(Into::into)), + tcx.mk_substs_from_iter(substs.into_iter().map(Into::into)), ) } diff --git a/src/tools/clippy/clippy_utils/src/visitors.rs b/src/tools/clippy/clippy_utils/src/visitors.rs index 14c01a60b..d27a20bd4 100644 --- a/src/tools/clippy/clippy_utils/src/visitors.rs +++ b/src/tools/clippy/clippy_utils/src/visitors.rs @@ -392,12 +392,16 @@ pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { .cx .typeck_results() .type_dependent_def_id(e.hir_id) - .map_or(false, |id| self.cx.tcx.fn_sig(id).unsafety() == Unsafety::Unsafe) => + .map_or(false, |id| { + self.cx.tcx.fn_sig(id).skip_binder().unsafety() == Unsafety::Unsafe + }) => { self.is_unsafe = true; }, ExprKind::Call(func, _) => match *self.cx.typeck_results().expr_ty(func).peel_refs().kind() { - ty::FnDef(id, _) if self.cx.tcx.fn_sig(id).unsafety() == Unsafety::Unsafe => self.is_unsafe = true, + ty::FnDef(id, _) if self.cx.tcx.fn_sig(id).skip_binder().unsafety() == Unsafety::Unsafe => { + self.is_unsafe = true; + }, ty::FnPtr(sig) if sig.unsafety() == Unsafety::Unsafe => self.is_unsafe = true, _ => walk_expr(self, e), }, @@ -661,7 +665,7 @@ pub fn for_each_unconsumed_temporary<'tcx, B>( | ExprKind::Path(_) | ExprKind::Continue(_) | ExprKind::InlineAsm(_) - | ExprKind::Err => (), + | ExprKind::Err(_) => (), } ControlFlow::Continue(()) } diff --git a/src/tools/clippy/declare_clippy_lint/Cargo.toml b/src/tools/clippy/declare_clippy_lint/Cargo.toml index c01e1062c..80eee3681 100644 --- a/src/tools/clippy/declare_clippy_lint/Cargo.toml +++ b/src/tools/clippy/declare_clippy_lint/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "declare_clippy_lint" -version = "0.1.68" +version = "0.1.69" edition = "2021" publish = false diff --git a/src/tools/clippy/lintcheck/Cargo.toml b/src/tools/clippy/lintcheck/Cargo.toml index de31c16b8..653121af5 100644 --- a/src/tools/clippy/lintcheck/Cargo.toml +++ b/src/tools/clippy/lintcheck/Cargo.toml @@ -10,8 +10,8 @@ edition = "2021" publish = false [dependencies] -cargo_metadata = "0.14" -clap = "3.2" +cargo_metadata = "0.15.3" +clap = "4.1.4" crossbeam-channel = "0.5.6" flate2 = "1.0" rayon = "1.5.1" diff --git a/src/tools/clippy/lintcheck/src/config.rs b/src/tools/clippy/lintcheck/src/config.rs index b8824024e..e0244ddce 100644 --- a/src/tools/clippy/lintcheck/src/config.rs +++ b/src/tools/clippy/lintcheck/src/config.rs @@ -35,7 +35,7 @@ fn get_clap_config() -> ArgMatches { .long("markdown") .help("Change the reports table to use markdown links"), Arg::new("recursive") - .long("--recursive") + .long("recursive") .help("Run clippy on the dependencies of crates specified in crates-toml") .conflicts_with("threads") .conflicts_with("fix"), diff --git a/src/tools/clippy/lintcheck/src/main.rs b/src/tools/clippy/lintcheck/src/main.rs index bd49f0960..23c852980 100644 --- a/src/tools/clippy/lintcheck/src/main.rs +++ b/src/tools/clippy/lintcheck/src/main.rs @@ -17,9 +17,9 @@ use crate::recursive::LintcheckServer; use std::collections::{HashMap, HashSet}; use std::env; use std::env::consts::EXE_SUFFIX; -use std::fmt::Write as _; +use std::fmt::{self, Write as _}; use std::fs; -use std::io::ErrorKind; +use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -145,8 +145,8 @@ impl ClippyWarning { } let mut output = String::from("| "); - let _ = write!(output, "[`{file_with_pos}`]({file}#L{})", self.line); - let _ = write!(output, r#" | `{:<50}` | "{}" |"#, self.lint_type, self.message); + let _: fmt::Result = write!(output, "[`{file_with_pos}`]({file}#L{})", self.line); + let _: fmt::Result = write!(output, r#" | `{:<50}` | "{}" |"#, self.lint_type, self.message); output.push('\n'); output } else { @@ -632,7 +632,7 @@ fn main() { .unwrap(); let server = config.recursive.then(|| { - let _ = fs::remove_dir_all("target/lintcheck/shared_target_dir/recursive"); + let _: io::Result<()> = fs::remove_dir_all("target/lintcheck/shared_target_dir/recursive"); LintcheckServer::spawn(recursive_options) }); @@ -689,7 +689,7 @@ fn main() { write!(text, "{}", all_msgs.join("")).unwrap(); text.push_str("\n\n### ICEs:\n"); for (cratename, msg) in &ices { - let _ = write!(text, "{cratename}: '{msg}'"); + let _: fmt::Result = write!(text, "{cratename}: '{msg}'"); } println!("Writing logs to {}", config.lintcheck_results_path.display()); diff --git a/src/tools/clippy/rust-toolchain b/src/tools/clippy/rust-toolchain index 40a6f4709..cfe845ec7 100644 --- a/src/tools/clippy/rust-toolchain +++ b/src/tools/clippy/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2023-01-12" +channel = "nightly-2023-02-25" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/src/tools/clippy/src/driver.rs b/src/tools/clippy/src/driver.rs index d521e8d88..dd183362f 100644 --- a/src/tools/clippy/src/driver.rs +++ b/src/tools/clippy/src/driver.rs @@ -209,7 +209,7 @@ fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) { // Separate the output with an empty line eprintln!(); - let fallback_bundle = rustc_errors::fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false); + let fallback_bundle = rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false); let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr( rustc_errors::ColorConfig::Auto, None, @@ -220,6 +220,7 @@ fn report_clippy_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) { None, false, false, + rustc_errors::TerminalUrl::No, )); let handler = rustc_errors::Handler::with_emitter(true, None, emitter); diff --git a/src/tools/clippy/src/main.rs b/src/tools/clippy/src/main.rs index 7a78b3262..82147eba3 100644 --- a/src/tools/clippy/src/main.rs +++ b/src/tools/clippy/src/main.rs @@ -28,7 +28,7 @@ with: -D --deny OPT Set lint denied -F --forbid OPT Set lint forbidden -You can use tool lints to allow or deny lints from your code, eg.: +You can use tool lints to allow or deny lints from your code, e.g.: #[allow(clippy::needless_lifetimes)] "#; diff --git a/src/tools/clippy/tests/ui-internal/custom_ice_message.rs b/src/tools/clippy/tests/ui-internal/custom_ice_message.rs index 4be04f77f..837811bdf 100644 --- a/src/tools/clippy/tests/ui-internal/custom_ice_message.rs +++ b/src/tools/clippy/tests/ui-internal/custom_ice_message.rs @@ -1,8 +1,9 @@ // rustc-env:RUST_BACKTRACE=0 // normalize-stderr-test: "Clippy version: .*" -> "Clippy version: foo" -// normalize-stderr-test: "internal_lints.rs:\d*:\d*" -> "internal_lints.rs" +// normalize-stderr-test: "produce_ice.rs:\d*:\d*" -> "produce_ice.rs" // normalize-stderr-test: "', .*clippy_lints" -> "', clippy_lints" // normalize-stderr-test: "'rustc'" -> "'<unnamed>'" +// normalize-stderr-test: "(?ms)query stack during panic:\n.*end of query stack\n" -> "" #![deny(clippy::internal)] #![allow(clippy::missing_clippy_version_attribute)] diff --git a/src/tools/clippy/tests/ui-internal/custom_ice_message.stderr b/src/tools/clippy/tests/ui-internal/custom_ice_message.stderr index 2ba589066..7ed0ef027 100644 --- a/src/tools/clippy/tests/ui-internal/custom_ice_message.stderr +++ b/src/tools/clippy/tests/ui-internal/custom_ice_message.stderr @@ -1,4 +1,4 @@ -thread '<unnamed>' panicked at 'Would you like some help with that?', clippy_lints/src/utils/internal_lints/produce_ice.rs:28:9 +thread '<unnamed>' panicked at 'Would you like some help with that?', clippy_lints/src/utils/internal_lints/produce_ice.rs note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: internal compiler error: unexpected panic @@ -9,5 +9,3 @@ note: we would appreciate a bug report: https://github.com/rust-lang/rust-clippy note: Clippy version: foo -query stack during panic: -end of query stack diff --git a/src/tools/clippy/tests/ui-toml/expect_used/expect_used.rs b/src/tools/clippy/tests/ui-toml/expect_used/expect_used.rs index bff97d97d..89f142a15 100644 --- a/src/tools/clippy/tests/ui-toml/expect_used/expect_used.rs +++ b/src/tools/clippy/tests/ui-toml/expect_used/expect_used.rs @@ -16,6 +16,18 @@ fn main() { expect_result(); } +#[test] +fn test_expect_option() { + let opt = Some(0); + let _ = opt.expect(""); +} + +#[test] +fn test_expect_result() { + let res: Result<u8, ()> = Ok(0); + let _ = res.expect(""); +} + #[cfg(test)] mod issue9612 { // should not lint in `#[cfg(test)]` modules diff --git a/src/tools/clippy/tests/ui-toml/pub_crate_missing_docs/clippy.toml b/src/tools/clippy/tests/ui-toml/pub_crate_missing_docs/clippy.toml new file mode 100644 index 000000000..ec210a987 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/pub_crate_missing_docs/clippy.toml @@ -0,0 +1 @@ +missing-docs-in-crate-items = true diff --git a/src/tools/clippy/tests/ui-toml/pub_crate_missing_docs/pub_crate_missing_doc.rs b/src/tools/clippy/tests/ui-toml/pub_crate_missing_docs/pub_crate_missing_doc.rs new file mode 100644 index 000000000..830d71f61 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/pub_crate_missing_docs/pub_crate_missing_doc.rs @@ -0,0 +1,59 @@ +//! this is crate +#![allow(missing_docs)] +#![warn(clippy::missing_docs_in_private_items)] + +/// this is mod +mod my_mod { + /// some docs + fn priv_with_docs() {} + fn priv_no_docs() {} + /// some docs + pub(crate) fn crate_with_docs() {} + pub(crate) fn crate_no_docs() {} + /// some docs + pub(super) fn super_with_docs() {} + pub(super) fn super_no_docs() {} + + mod my_sub { + /// some docs + fn sub_priv_with_docs() {} + fn sub_priv_no_docs() {} + /// some docs + pub(crate) fn sub_crate_with_docs() {} + pub(crate) fn sub_crate_no_docs() {} + /// some docs + pub(super) fn sub_super_with_docs() {} + pub(super) fn sub_super_no_docs() {} + } + + /// some docs + pub(crate) struct CrateStructWithDocs { + /// some docs + pub(crate) crate_field_with_docs: (), + pub(crate) crate_field_no_docs: (), + /// some docs + priv_field_with_docs: (), + priv_field_no_docs: (), + } + + pub(crate) struct CrateStructNoDocs { + /// some docs + pub(crate) crate_field_with_docs: (), + pub(crate) crate_field_no_docs: (), + /// some docs + priv_field_with_docs: (), + priv_field_no_docs: (), + } +} + +/// some docs +type CrateTypedefWithDocs = String; +type CrateTypedefNoDocs = String; +/// some docs +pub type PubTypedefWithDocs = String; +pub type PubTypedefNoDocs = String; + +fn main() { + my_mod::crate_with_docs(); + my_mod::crate_no_docs(); +} diff --git a/src/tools/clippy/tests/ui-toml/pub_crate_missing_docs/pub_crate_missing_doc.stderr b/src/tools/clippy/tests/ui-toml/pub_crate_missing_docs/pub_crate_missing_doc.stderr new file mode 100644 index 000000000..a47418705 --- /dev/null +++ b/src/tools/clippy/tests/ui-toml/pub_crate_missing_docs/pub_crate_missing_doc.stderr @@ -0,0 +1,52 @@ +error: missing documentation for a function + --> $DIR/pub_crate_missing_doc.rs:12:5 + | +LL | pub(crate) fn crate_no_docs() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::missing-docs-in-private-items` implied by `-D warnings` + +error: missing documentation for a function + --> $DIR/pub_crate_missing_doc.rs:15:5 + | +LL | pub(super) fn super_no_docs() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a function + --> $DIR/pub_crate_missing_doc.rs:23:9 + | +LL | pub(crate) fn sub_crate_no_docs() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a struct field + --> $DIR/pub_crate_missing_doc.rs:33:9 + | +LL | pub(crate) crate_field_no_docs: (), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a struct + --> $DIR/pub_crate_missing_doc.rs:39:5 + | +LL | / pub(crate) struct CrateStructNoDocs { +LL | | /// some docs +LL | | pub(crate) crate_field_with_docs: (), +LL | | pub(crate) crate_field_no_docs: (), +... | +LL | | priv_field_no_docs: (), +LL | | } + | |_____^ + +error: missing documentation for a struct field + --> $DIR/pub_crate_missing_doc.rs:42:9 + | +LL | pub(crate) crate_field_no_docs: (), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: missing documentation for a type alias + --> $DIR/pub_crate_missing_doc.rs:51:1 + | +LL | type CrateTypedefNoDocs = String; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + diff --git a/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index a22c6a5a0..6a246afac 100644 --- a/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/src/tools/clippy/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -33,6 +33,7 @@ error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown fie max-struct-bools max-suggested-slice-pattern-length max-trait-bounds + missing-docs-in-crate-items msrv pass-by-value-size-limit single-char-binding-names-threshold diff --git a/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.rs b/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.rs index bc8e8c1f0..6525ea5bf 100644 --- a/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.rs +++ b/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.rs @@ -66,6 +66,12 @@ fn main() { } } +#[test] +fn test() { + let boxed_slice: Box<[u8]> = Box::new([0, 1, 2, 3]); + let _ = boxed_slice.get(1).unwrap(); +} + #[cfg(test)] mod issue9612 { // should not lint in `#[cfg(test)]` modules diff --git a/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.stderr b/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.stderr index 94b5ef663..8a32750e3 100644 --- a/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.stderr +++ b/src/tools/clippy/tests/ui-toml/unwrap_used/unwrap_used.stderr @@ -188,10 +188,16 @@ LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec(); = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/unwrap_used.rs:84:17 + --> $DIR/unwrap_used.rs:72:13 + | +LL | let _ = boxed_slice.get(1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&boxed_slice[1]` + +error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise + --> $DIR/unwrap_used.rs:90:17 | LL | let _ = Box::new([0]).get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&Box::new([0])[1]` -error: aborting due to 27 previous errors +error: aborting due to 28 previous errors diff --git a/src/tools/clippy/tests/ui/arithmetic_side_effects.rs b/src/tools/clippy/tests/ui/arithmetic_side_effects.rs index 918cf81c6..2611e3a78 100644 --- a/src/tools/clippy/tests/ui/arithmetic_side_effects.rs +++ b/src/tools/clippy/tests/ui/arithmetic_side_effects.rs @@ -13,6 +13,9 @@ use core::num::{Saturating, Wrapping}; +const ONE: i32 = 1; +const ZERO: i32 = 0; + #[derive(Clone, Copy)] pub struct Custom; @@ -182,6 +185,10 @@ pub fn non_overflowing_ops_or_ops_already_handled_by_the_compiler_should_not_tri _n += &0; _n -= 0; _n -= &0; + _n += ZERO; + _n += &ZERO; + _n -= ZERO; + _n -= &ZERO; _n /= 99; _n /= &99; _n %= 99; @@ -190,10 +197,18 @@ pub fn non_overflowing_ops_or_ops_already_handled_by_the_compiler_should_not_tri _n *= &0; _n *= 1; _n *= &1; + _n *= ZERO; + _n *= &ZERO; + _n *= ONE; + _n *= &ONE; _n += -0; _n += &-0; _n -= -0; _n -= &-0; + _n += -ZERO; + _n += &-ZERO; + _n -= -ZERO; + _n -= &-ZERO; _n /= -99; _n /= &-99; _n %= -99; @@ -208,10 +223,18 @@ pub fn non_overflowing_ops_or_ops_already_handled_by_the_compiler_should_not_tri _n = _n + &0; _n = 0 + _n; _n = &0 + _n; + _n = _n + ZERO; + _n = _n + &ZERO; + _n = ZERO + _n; + _n = &ZERO + _n; _n = _n - 0; _n = _n - &0; _n = 0 - _n; _n = &0 - _n; + _n = _n - ZERO; + _n = _n - &ZERO; + _n = ZERO - _n; + _n = &ZERO - _n; _n = _n / 99; _n = _n / &99; _n = _n % 99; @@ -222,6 +245,10 @@ pub fn non_overflowing_ops_or_ops_already_handled_by_the_compiler_should_not_tri _n = &0 * _n; _n = _n * 1; _n = _n * &1; + _n = ZERO * _n; + _n = &ZERO * _n; + _n = _n * ONE; + _n = _n * &ONE; _n = 1 * _n; _n = &1 * _n; _n = 23 + 85; diff --git a/src/tools/clippy/tests/ui/arithmetic_side_effects.stderr b/src/tools/clippy/tests/ui/arithmetic_side_effects.stderr index 5e349f6b4..17a2448fb 100644 --- a/src/tools/clippy/tests/ui/arithmetic_side_effects.stderr +++ b/src/tools/clippy/tests/ui/arithmetic_side_effects.stderr @@ -1,5 +1,5 @@ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:243:5 + --> $DIR/arithmetic_side_effects.rs:270:5 | LL | _n += 1; | ^^^^^^^ @@ -7,589 +7,589 @@ LL | _n += 1; = note: `-D clippy::arithmetic-side-effects` implied by `-D warnings` error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:244:5 + --> $DIR/arithmetic_side_effects.rs:271:5 | LL | _n += &1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:245:5 + --> $DIR/arithmetic_side_effects.rs:272:5 | LL | _n -= 1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:246:5 + --> $DIR/arithmetic_side_effects.rs:273:5 | LL | _n -= &1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:247:5 + --> $DIR/arithmetic_side_effects.rs:274:5 | LL | _n /= 0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:248:5 + --> $DIR/arithmetic_side_effects.rs:275:5 | LL | _n /= &0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:249:5 + --> $DIR/arithmetic_side_effects.rs:276:5 | LL | _n %= 0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:250:5 + --> $DIR/arithmetic_side_effects.rs:277:5 | LL | _n %= &0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:251:5 + --> $DIR/arithmetic_side_effects.rs:278:5 | LL | _n *= 2; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:252:5 + --> $DIR/arithmetic_side_effects.rs:279:5 | LL | _n *= &2; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:253:5 + --> $DIR/arithmetic_side_effects.rs:280:5 | LL | _n += -1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:254:5 + --> $DIR/arithmetic_side_effects.rs:281:5 | LL | _n += &-1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:255:5 + --> $DIR/arithmetic_side_effects.rs:282:5 | LL | _n -= -1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:256:5 + --> $DIR/arithmetic_side_effects.rs:283:5 | LL | _n -= &-1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:257:5 + --> $DIR/arithmetic_side_effects.rs:284:5 | LL | _n /= -0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:258:5 + --> $DIR/arithmetic_side_effects.rs:285:5 | LL | _n /= &-0; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:259:5 + --> $DIR/arithmetic_side_effects.rs:286:5 | LL | _n %= -0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:260:5 + --> $DIR/arithmetic_side_effects.rs:287:5 | LL | _n %= &-0; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:261:5 + --> $DIR/arithmetic_side_effects.rs:288:5 | LL | _n *= -2; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:262:5 + --> $DIR/arithmetic_side_effects.rs:289:5 | LL | _n *= &-2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:263:5 + --> $DIR/arithmetic_side_effects.rs:290:5 | LL | _custom += Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:264:5 + --> $DIR/arithmetic_side_effects.rs:291:5 | LL | _custom += &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:265:5 + --> $DIR/arithmetic_side_effects.rs:292:5 | LL | _custom -= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:266:5 + --> $DIR/arithmetic_side_effects.rs:293:5 | LL | _custom -= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:267:5 + --> $DIR/arithmetic_side_effects.rs:294:5 | LL | _custom /= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:268:5 + --> $DIR/arithmetic_side_effects.rs:295:5 | LL | _custom /= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:269:5 + --> $DIR/arithmetic_side_effects.rs:296:5 | LL | _custom %= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:270:5 + --> $DIR/arithmetic_side_effects.rs:297:5 | LL | _custom %= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:271:5 + --> $DIR/arithmetic_side_effects.rs:298:5 | LL | _custom *= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:272:5 + --> $DIR/arithmetic_side_effects.rs:299:5 | LL | _custom *= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:273:5 + --> $DIR/arithmetic_side_effects.rs:300:5 | LL | _custom += -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:274:5 + --> $DIR/arithmetic_side_effects.rs:301:5 | LL | _custom += &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:275:5 + --> $DIR/arithmetic_side_effects.rs:302:5 | LL | _custom -= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:276:5 + --> $DIR/arithmetic_side_effects.rs:303:5 | LL | _custom -= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:277:5 + --> $DIR/arithmetic_side_effects.rs:304:5 | LL | _custom /= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:278:5 + --> $DIR/arithmetic_side_effects.rs:305:5 | LL | _custom /= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:279:5 + --> $DIR/arithmetic_side_effects.rs:306:5 | LL | _custom %= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:280:5 + --> $DIR/arithmetic_side_effects.rs:307:5 | LL | _custom %= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:281:5 + --> $DIR/arithmetic_side_effects.rs:308:5 | LL | _custom *= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:282:5 + --> $DIR/arithmetic_side_effects.rs:309:5 | LL | _custom *= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:285:10 + --> $DIR/arithmetic_side_effects.rs:312:10 | LL | _n = _n + 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:286:10 + --> $DIR/arithmetic_side_effects.rs:313:10 | LL | _n = _n + &1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:287:10 + --> $DIR/arithmetic_side_effects.rs:314:10 | LL | _n = 1 + _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:288:10 + --> $DIR/arithmetic_side_effects.rs:315:10 | LL | _n = &1 + _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:289:10 + --> $DIR/arithmetic_side_effects.rs:316:10 | LL | _n = _n - 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:290:10 + --> $DIR/arithmetic_side_effects.rs:317:10 | LL | _n = _n - &1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:291:10 + --> $DIR/arithmetic_side_effects.rs:318:10 | LL | _n = 1 - _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:292:10 + --> $DIR/arithmetic_side_effects.rs:319:10 | LL | _n = &1 - _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:293:10 + --> $DIR/arithmetic_side_effects.rs:320:10 | LL | _n = _n / 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:294:10 + --> $DIR/arithmetic_side_effects.rs:321:10 | LL | _n = _n / &0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:295:10 + --> $DIR/arithmetic_side_effects.rs:322:10 | LL | _n = _n % 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:296:10 + --> $DIR/arithmetic_side_effects.rs:323:10 | LL | _n = _n % &0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:297:10 + --> $DIR/arithmetic_side_effects.rs:324:10 | LL | _n = _n * 2; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:298:10 + --> $DIR/arithmetic_side_effects.rs:325:10 | LL | _n = _n * &2; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:299:10 + --> $DIR/arithmetic_side_effects.rs:326:10 | LL | _n = 2 * _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:300:10 + --> $DIR/arithmetic_side_effects.rs:327:10 | LL | _n = &2 * _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:301:10 + --> $DIR/arithmetic_side_effects.rs:328:10 | LL | _n = 23 + &85; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:302:10 + --> $DIR/arithmetic_side_effects.rs:329:10 | LL | _n = &23 + 85; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:303:10 + --> $DIR/arithmetic_side_effects.rs:330:10 | LL | _n = &23 + &85; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:304:15 + --> $DIR/arithmetic_side_effects.rs:331:15 | LL | _custom = _custom + _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:305:15 + --> $DIR/arithmetic_side_effects.rs:332:15 | LL | _custom = _custom + &_custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:306:15 + --> $DIR/arithmetic_side_effects.rs:333:15 | LL | _custom = Custom + _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:307:15 + --> $DIR/arithmetic_side_effects.rs:334:15 | LL | _custom = &Custom + _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:308:15 + --> $DIR/arithmetic_side_effects.rs:335:15 | LL | _custom = _custom - Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:309:15 + --> $DIR/arithmetic_side_effects.rs:336:15 | LL | _custom = _custom - &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:310:15 + --> $DIR/arithmetic_side_effects.rs:337:15 | LL | _custom = Custom - _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:311:15 + --> $DIR/arithmetic_side_effects.rs:338:15 | LL | _custom = &Custom - _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:312:15 + --> $DIR/arithmetic_side_effects.rs:339:15 | LL | _custom = _custom / Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:313:15 + --> $DIR/arithmetic_side_effects.rs:340:15 | LL | _custom = _custom / &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:314:15 + --> $DIR/arithmetic_side_effects.rs:341:15 | LL | _custom = _custom % Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:315:15 + --> $DIR/arithmetic_side_effects.rs:342:15 | LL | _custom = _custom % &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:316:15 + --> $DIR/arithmetic_side_effects.rs:343:15 | LL | _custom = _custom * Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:317:15 + --> $DIR/arithmetic_side_effects.rs:344:15 | LL | _custom = _custom * &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:318:15 + --> $DIR/arithmetic_side_effects.rs:345:15 | LL | _custom = Custom * _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:319:15 + --> $DIR/arithmetic_side_effects.rs:346:15 | LL | _custom = &Custom * _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:320:15 + --> $DIR/arithmetic_side_effects.rs:347:15 | LL | _custom = Custom + &Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:321:15 + --> $DIR/arithmetic_side_effects.rs:348:15 | LL | _custom = &Custom + Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:322:15 + --> $DIR/arithmetic_side_effects.rs:349:15 | LL | _custom = &Custom + &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:325:10 + --> $DIR/arithmetic_side_effects.rs:352:10 | LL | _n = -_n; | ^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:326:10 + --> $DIR/arithmetic_side_effects.rs:353:10 | LL | _n = -&_n; | ^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:327:15 + --> $DIR/arithmetic_side_effects.rs:354:15 | LL | _custom = -_custom; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:328:15 + --> $DIR/arithmetic_side_effects.rs:355:15 | LL | _custom = -&_custom; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:337:5 + --> $DIR/arithmetic_side_effects.rs:364:5 | LL | 1 + i; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:338:5 + --> $DIR/arithmetic_side_effects.rs:365:5 | LL | i * 2; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:340:5 + --> $DIR/arithmetic_side_effects.rs:367:5 | LL | i - 2 + 2 - i; | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:341:5 + --> $DIR/arithmetic_side_effects.rs:368:5 | LL | -i; | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:342:5 + --> $DIR/arithmetic_side_effects.rs:369:5 | LL | i >> 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:343:5 + --> $DIR/arithmetic_side_effects.rs:370:5 | LL | i << 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:352:5 + --> $DIR/arithmetic_side_effects.rs:379:5 | LL | i += 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:353:5 + --> $DIR/arithmetic_side_effects.rs:380:5 | LL | i -= 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:354:5 + --> $DIR/arithmetic_side_effects.rs:381:5 | LL | i *= 2; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:356:5 + --> $DIR/arithmetic_side_effects.rs:383:5 | LL | i /= 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:358:5 + --> $DIR/arithmetic_side_effects.rs:385:5 | LL | i /= var1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:359:5 + --> $DIR/arithmetic_side_effects.rs:386:5 | LL | i /= var2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:361:5 + --> $DIR/arithmetic_side_effects.rs:388:5 | LL | i %= 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:363:5 + --> $DIR/arithmetic_side_effects.rs:390:5 | LL | i %= var1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:364:5 + --> $DIR/arithmetic_side_effects.rs:391:5 | LL | i %= var2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:365:5 + --> $DIR/arithmetic_side_effects.rs:392:5 | LL | i <<= 3; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> $DIR/arithmetic_side_effects.rs:366:5 + --> $DIR/arithmetic_side_effects.rs:393:5 | LL | i >>= 2; | ^^^^^^^ diff --git a/src/tools/clippy/tests/ui/auxiliary/macro_rules.rs b/src/tools/clippy/tests/ui/auxiliary/macro_rules.rs index 1e5f20e8c..a13af5652 100644 --- a/src/tools/clippy/tests/ui/auxiliary/macro_rules.rs +++ b/src/tools/clippy/tests/ui/auxiliary/macro_rules.rs @@ -149,3 +149,22 @@ macro_rules! almost_complete_range { let _ = '0'..'9'; }; } + +#[macro_export] +macro_rules! unsafe_macro { + () => { + unsafe { + *core::ptr::null::<()>(); + *core::ptr::null::<()>(); + } + }; +} + +#[macro_export] +macro_rules! needless_lifetime { + () => { + fn needless_lifetime<'a>(x: &'a u8) -> &'a u8 { + unimplemented!() + } + }; +} diff --git a/src/tools/clippy/tests/ui/bool_assert_comparison.fixed b/src/tools/clippy/tests/ui/bool_assert_comparison.fixed new file mode 100644 index 000000000..b8dd92906 --- /dev/null +++ b/src/tools/clippy/tests/ui/bool_assert_comparison.fixed @@ -0,0 +1,171 @@ +// run-rustfix + +#![allow(unused, clippy::assertions_on_constants)] +#![warn(clippy::bool_assert_comparison)] + +use std::ops::Not; + +macro_rules! a { + () => { + true + }; +} +macro_rules! b { + () => { + true + }; +} + +// Implements the Not trait but with an output type +// that's not bool. Should not suggest a rewrite +#[derive(Debug, Clone, Copy)] +enum ImplNotTraitWithoutBool { + VariantX(bool), + VariantY(u32), +} + +impl PartialEq<bool> for ImplNotTraitWithoutBool { + fn eq(&self, other: &bool) -> bool { + match *self { + ImplNotTraitWithoutBool::VariantX(b) => b == *other, + _ => false, + } + } +} + +impl Not for ImplNotTraitWithoutBool { + type Output = Self; + + fn not(self) -> Self::Output { + match self { + ImplNotTraitWithoutBool::VariantX(b) => ImplNotTraitWithoutBool::VariantX(!b), + ImplNotTraitWithoutBool::VariantY(0) => ImplNotTraitWithoutBool::VariantY(1), + ImplNotTraitWithoutBool::VariantY(_) => ImplNotTraitWithoutBool::VariantY(0), + } + } +} + +// This type implements the Not trait with an Output of +// type bool. Using assert!(..) must be suggested +#[derive(Debug, Clone, Copy)] +struct ImplNotTraitWithBool; + +impl PartialEq<bool> for ImplNotTraitWithBool { + fn eq(&self, other: &bool) -> bool { + false + } +} + +impl Not for ImplNotTraitWithBool { + type Output = bool; + + fn not(self) -> Self::Output { + true + } +} + +#[derive(Debug)] +struct NonCopy; + +impl PartialEq<bool> for NonCopy { + fn eq(&self, other: &bool) -> bool { + false + } +} + +impl Not for NonCopy { + type Output = bool; + + fn not(self) -> Self::Output { + true + } +} + +fn main() { + let a = ImplNotTraitWithoutBool::VariantX(true); + let b = ImplNotTraitWithBool; + + assert_eq!("a".len(), 1); + assert!(!"a".is_empty()); + assert!("".is_empty()); + assert!("".is_empty()); + assert_eq!(a!(), b!()); + assert_eq!(a!(), "".is_empty()); + assert_eq!("".is_empty(), b!()); + assert_eq!(a, true); + assert!(b); + + assert_ne!("a".len(), 1); + assert!("a".is_empty()); + assert!(!"".is_empty()); + assert!(!"".is_empty()); + assert_ne!(a!(), b!()); + assert_ne!(a!(), "".is_empty()); + assert_ne!("".is_empty(), b!()); + assert_ne!(a, true); + assert!(!b); + + debug_assert_eq!("a".len(), 1); + debug_assert!(!"a".is_empty()); + debug_assert!("".is_empty()); + debug_assert!("".is_empty()); + debug_assert_eq!(a!(), b!()); + debug_assert_eq!(a!(), "".is_empty()); + debug_assert_eq!("".is_empty(), b!()); + debug_assert_eq!(a, true); + debug_assert!(b); + + debug_assert_ne!("a".len(), 1); + debug_assert!("a".is_empty()); + debug_assert!(!"".is_empty()); + debug_assert!(!"".is_empty()); + debug_assert_ne!(a!(), b!()); + debug_assert_ne!(a!(), "".is_empty()); + debug_assert_ne!("".is_empty(), b!()); + debug_assert_ne!(a, true); + debug_assert!(!b); + + // assert with error messages + assert_eq!("a".len(), 1, "tadam {}", 1); + assert_eq!("a".len(), 1, "tadam {}", true); + assert!(!"a".is_empty(), "tadam {}", 1); + assert!(!"a".is_empty(), "tadam {}", true); + assert!(!"a".is_empty(), "tadam {}", true); + assert_eq!(a, true, "tadam {}", false); + + debug_assert_eq!("a".len(), 1, "tadam {}", 1); + debug_assert_eq!("a".len(), 1, "tadam {}", true); + debug_assert!(!"a".is_empty(), "tadam {}", 1); + debug_assert!(!"a".is_empty(), "tadam {}", true); + debug_assert!(!"a".is_empty(), "tadam {}", true); + debug_assert_eq!(a, true, "tadam {}", false); + + assert!(a!()); + assert!(b!()); + + use debug_assert_eq as renamed; + renamed!(a, true); + debug_assert!(b); + + let non_copy = NonCopy; + assert_eq!(non_copy, true); + // changing the above to `assert!(non_copy)` would cause a `borrow of moved value` + println!("{non_copy:?}"); + + macro_rules! in_macro { + ($v:expr) => {{ + assert_eq!($v, true); + }}; + } + in_macro!(a); + + assert!("".is_empty()); + assert!("".is_empty()); + assert!(!"requires negation".is_empty()); + assert!(!"requires negation".is_empty()); + + debug_assert!("".is_empty()); + debug_assert!("".is_empty()); + debug_assert!(!"requires negation".is_empty()); + debug_assert!(!"requires negation".is_empty()); +} diff --git a/src/tools/clippy/tests/ui/bool_assert_comparison.rs b/src/tools/clippy/tests/ui/bool_assert_comparison.rs index ec4d6f3ff..0a8ad34fd 100644 --- a/src/tools/clippy/tests/ui/bool_assert_comparison.rs +++ b/src/tools/clippy/tests/ui/bool_assert_comparison.rs @@ -1,3 +1,6 @@ +// run-rustfix + +#![allow(unused, clippy::assertions_on_constants)] #![warn(clippy::bool_assert_comparison)] use std::ops::Not; @@ -15,7 +18,7 @@ macro_rules! b { // Implements the Not trait but with an output type // that's not bool. Should not suggest a rewrite -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] enum ImplNotTraitWithoutBool { VariantX(bool), VariantY(u32), @@ -44,7 +47,7 @@ impl Not for ImplNotTraitWithoutBool { // This type implements the Not trait with an Output of // type bool. Using assert!(..) must be suggested -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] struct ImplNotTraitWithBool; impl PartialEq<bool> for ImplNotTraitWithBool { @@ -61,6 +64,23 @@ impl Not for ImplNotTraitWithBool { } } +#[derive(Debug)] +struct NonCopy; + +impl PartialEq<bool> for NonCopy { + fn eq(&self, other: &bool) -> bool { + false + } +} + +impl Not for NonCopy { + type Output = bool; + + fn not(self) -> Self::Output { + true + } +} + fn main() { let a = ImplNotTraitWithoutBool::VariantX(true); let b = ImplNotTraitWithBool; @@ -119,4 +139,33 @@ fn main() { debug_assert_eq!("a".is_empty(), false, "tadam {}", true); debug_assert_eq!(false, "a".is_empty(), "tadam {}", true); debug_assert_eq!(a, true, "tadam {}", false); + + assert_eq!(a!(), true); + assert_eq!(true, b!()); + + use debug_assert_eq as renamed; + renamed!(a, true); + renamed!(b, true); + + let non_copy = NonCopy; + assert_eq!(non_copy, true); + // changing the above to `assert!(non_copy)` would cause a `borrow of moved value` + println!("{non_copy:?}"); + + macro_rules! in_macro { + ($v:expr) => {{ + assert_eq!($v, true); + }}; + } + in_macro!(a); + + assert_eq!("".is_empty(), true); + assert_ne!("".is_empty(), false); + assert_ne!("requires negation".is_empty(), true); + assert_eq!("requires negation".is_empty(), false); + + debug_assert_eq!("".is_empty(), true); + debug_assert_ne!("".is_empty(), false); + debug_assert_ne!("requires negation".is_empty(), true); + debug_assert_eq!("requires negation".is_empty(), false); } diff --git a/src/tools/clippy/tests/ui/bool_assert_comparison.stderr b/src/tools/clippy/tests/ui/bool_assert_comparison.stderr index 377d51be4..89cefc95a 100644 --- a/src/tools/clippy/tests/ui/bool_assert_comparison.stderr +++ b/src/tools/clippy/tests/ui/bool_assert_comparison.stderr @@ -1,136 +1,399 @@ error: used `assert_eq!` with a literal bool - --> $DIR/bool_assert_comparison.rs:69:5 + --> $DIR/bool_assert_comparison.rs:89:5 | LL | assert_eq!("a".is_empty(), false); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::bool-assert-comparison` implied by `-D warnings` +help: replace it with `assert!(..)` + | +LL - assert_eq!("a".is_empty(), false); +LL + assert!(!"a".is_empty()); + | error: used `assert_eq!` with a literal bool - --> $DIR/bool_assert_comparison.rs:70:5 + --> $DIR/bool_assert_comparison.rs:90:5 | LL | assert_eq!("".is_empty(), true); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_eq!("".is_empty(), true); +LL + assert!("".is_empty()); + | error: used `assert_eq!` with a literal bool - --> $DIR/bool_assert_comparison.rs:71:5 + --> $DIR/bool_assert_comparison.rs:91:5 | LL | assert_eq!(true, "".is_empty()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_eq!(true, "".is_empty()); +LL + assert!("".is_empty()); + | error: used `assert_eq!` with a literal bool - --> $DIR/bool_assert_comparison.rs:76:5 + --> $DIR/bool_assert_comparison.rs:96:5 | LL | assert_eq!(b, true); - | ^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)` + | ^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_eq!(b, true); +LL + assert!(b); + | error: used `assert_ne!` with a literal bool - --> $DIR/bool_assert_comparison.rs:79:5 + --> $DIR/bool_assert_comparison.rs:99:5 | LL | assert_ne!("a".is_empty(), false); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_ne!("a".is_empty(), false); +LL + assert!("a".is_empty()); + | error: used `assert_ne!` with a literal bool - --> $DIR/bool_assert_comparison.rs:80:5 + --> $DIR/bool_assert_comparison.rs:100:5 | LL | assert_ne!("".is_empty(), true); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_ne!("".is_empty(), true); +LL + assert!(!"".is_empty()); + | error: used `assert_ne!` with a literal bool - --> $DIR/bool_assert_comparison.rs:81:5 + --> $DIR/bool_assert_comparison.rs:101:5 | LL | assert_ne!(true, "".is_empty()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_ne!(true, "".is_empty()); +LL + assert!(!"".is_empty()); + | error: used `assert_ne!` with a literal bool - --> $DIR/bool_assert_comparison.rs:86:5 + --> $DIR/bool_assert_comparison.rs:106:5 | LL | assert_ne!(b, true); - | ^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)` + | ^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_ne!(b, true); +LL + assert!(!b); + | error: used `debug_assert_eq!` with a literal bool - --> $DIR/bool_assert_comparison.rs:89:5 + --> $DIR/bool_assert_comparison.rs:109:5 | LL | debug_assert_eq!("a".is_empty(), false); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert!(..)` + | +LL - debug_assert_eq!("a".is_empty(), false); +LL + debug_assert!(!"a".is_empty()); + | error: used `debug_assert_eq!` with a literal bool - --> $DIR/bool_assert_comparison.rs:90:5 + --> $DIR/bool_assert_comparison.rs:110:5 | LL | debug_assert_eq!("".is_empty(), true); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert!(..)` + | +LL - debug_assert_eq!("".is_empty(), true); +LL + debug_assert!("".is_empty()); + | error: used `debug_assert_eq!` with a literal bool - --> $DIR/bool_assert_comparison.rs:91:5 + --> $DIR/bool_assert_comparison.rs:111:5 | LL | debug_assert_eq!(true, "".is_empty()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert!(..)` + | +LL - debug_assert_eq!(true, "".is_empty()); +LL + debug_assert!("".is_empty()); + | error: used `debug_assert_eq!` with a literal bool - --> $DIR/bool_assert_comparison.rs:96:5 + --> $DIR/bool_assert_comparison.rs:116:5 | LL | debug_assert_eq!(b, true); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert!(..)` + | +LL - debug_assert_eq!(b, true); +LL + debug_assert!(b); + | error: used `debug_assert_ne!` with a literal bool - --> $DIR/bool_assert_comparison.rs:99:5 + --> $DIR/bool_assert_comparison.rs:119:5 | LL | debug_assert_ne!("a".is_empty(), false); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert!(..)` + | +LL - debug_assert_ne!("a".is_empty(), false); +LL + debug_assert!("a".is_empty()); + | error: used `debug_assert_ne!` with a literal bool - --> $DIR/bool_assert_comparison.rs:100:5 + --> $DIR/bool_assert_comparison.rs:120:5 | LL | debug_assert_ne!("".is_empty(), true); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert!(..)` + | +LL - debug_assert_ne!("".is_empty(), true); +LL + debug_assert!(!"".is_empty()); + | error: used `debug_assert_ne!` with a literal bool - --> $DIR/bool_assert_comparison.rs:101:5 + --> $DIR/bool_assert_comparison.rs:121:5 | LL | debug_assert_ne!(true, "".is_empty()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert!(..)` + | +LL - debug_assert_ne!(true, "".is_empty()); +LL + debug_assert!(!"".is_empty()); + | error: used `debug_assert_ne!` with a literal bool - --> $DIR/bool_assert_comparison.rs:106:5 + --> $DIR/bool_assert_comparison.rs:126:5 | LL | debug_assert_ne!(b, true); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert!(..)` + | +LL - debug_assert_ne!(b, true); +LL + debug_assert!(!b); + | error: used `assert_eq!` with a literal bool - --> $DIR/bool_assert_comparison.rs:111:5 + --> $DIR/bool_assert_comparison.rs:131:5 | LL | assert_eq!("a".is_empty(), false, "tadam {}", 1); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_eq!("a".is_empty(), false, "tadam {}", 1); +LL + assert!(!"a".is_empty(), "tadam {}", 1); + | error: used `assert_eq!` with a literal bool - --> $DIR/bool_assert_comparison.rs:112:5 + --> $DIR/bool_assert_comparison.rs:132:5 | LL | assert_eq!("a".is_empty(), false, "tadam {}", true); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_eq!("a".is_empty(), false, "tadam {}", true); +LL + assert!(!"a".is_empty(), "tadam {}", true); + | error: used `assert_eq!` with a literal bool - --> $DIR/bool_assert_comparison.rs:113:5 + --> $DIR/bool_assert_comparison.rs:133:5 | LL | assert_eq!(false, "a".is_empty(), "tadam {}", true); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_eq!(false, "a".is_empty(), "tadam {}", true); +LL + assert!(!"a".is_empty(), "tadam {}", true); + | error: used `debug_assert_eq!` with a literal bool - --> $DIR/bool_assert_comparison.rs:118:5 + --> $DIR/bool_assert_comparison.rs:138:5 | LL | debug_assert_eq!("a".is_empty(), false, "tadam {}", 1); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert!(..)` + | +LL - debug_assert_eq!("a".is_empty(), false, "tadam {}", 1); +LL + debug_assert!(!"a".is_empty(), "tadam {}", 1); + | error: used `debug_assert_eq!` with a literal bool - --> $DIR/bool_assert_comparison.rs:119:5 + --> $DIR/bool_assert_comparison.rs:139:5 | LL | debug_assert_eq!("a".is_empty(), false, "tadam {}", true); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert!(..)` + | +LL - debug_assert_eq!("a".is_empty(), false, "tadam {}", true); +LL + debug_assert!(!"a".is_empty(), "tadam {}", true); + | error: used `debug_assert_eq!` with a literal bool - --> $DIR/bool_assert_comparison.rs:120:5 + --> $DIR/bool_assert_comparison.rs:140:5 | LL | debug_assert_eq!(false, "a".is_empty(), "tadam {}", true); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert!(..)` + | +LL - debug_assert_eq!(false, "a".is_empty(), "tadam {}", true); +LL + debug_assert!(!"a".is_empty(), "tadam {}", true); + | + +error: used `assert_eq!` with a literal bool + --> $DIR/bool_assert_comparison.rs:143:5 + | +LL | assert_eq!(a!(), true); + | ^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_eq!(a!(), true); +LL + assert!(a!()); + | + +error: used `assert_eq!` with a literal bool + --> $DIR/bool_assert_comparison.rs:144:5 + | +LL | assert_eq!(true, b!()); + | ^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_eq!(true, b!()); +LL + assert!(b!()); + | + +error: used `debug_assert_eq!` with a literal bool + --> $DIR/bool_assert_comparison.rs:148:5 + | +LL | renamed!(b, true); + | ^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert!(..)` + | +LL - renamed!(b, true); +LL + debug_assert!(b); + | + +error: used `assert_eq!` with a literal bool + --> $DIR/bool_assert_comparison.rs:162:5 + | +LL | assert_eq!("".is_empty(), true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_eq!("".is_empty(), true); +LL + assert!("".is_empty()); + | + +error: used `assert_ne!` with a literal bool + --> $DIR/bool_assert_comparison.rs:163:5 + | +LL | assert_ne!("".is_empty(), false); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_ne!("".is_empty(), false); +LL + assert!("".is_empty()); + | + +error: used `assert_ne!` with a literal bool + --> $DIR/bool_assert_comparison.rs:164:5 + | +LL | assert_ne!("requires negation".is_empty(), true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_ne!("requires negation".is_empty(), true); +LL + assert!(!"requires negation".is_empty()); + | + +error: used `assert_eq!` with a literal bool + --> $DIR/bool_assert_comparison.rs:165:5 + | +LL | assert_eq!("requires negation".is_empty(), false); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `assert!(..)` + | +LL - assert_eq!("requires negation".is_empty(), false); +LL + assert!(!"requires negation".is_empty()); + | + +error: used `debug_assert_eq!` with a literal bool + --> $DIR/bool_assert_comparison.rs:167:5 + | +LL | debug_assert_eq!("".is_empty(), true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert!(..)` + | +LL - debug_assert_eq!("".is_empty(), true); +LL + debug_assert!("".is_empty()); + | + +error: used `debug_assert_ne!` with a literal bool + --> $DIR/bool_assert_comparison.rs:168:5 + | +LL | debug_assert_ne!("".is_empty(), false); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert!(..)` + | +LL - debug_assert_ne!("".is_empty(), false); +LL + debug_assert!("".is_empty()); + | + +error: used `debug_assert_ne!` with a literal bool + --> $DIR/bool_assert_comparison.rs:169:5 + | +LL | debug_assert_ne!("requires negation".is_empty(), true); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert!(..)` + | +LL - debug_assert_ne!("requires negation".is_empty(), true); +LL + debug_assert!(!"requires negation".is_empty()); + | + +error: used `debug_assert_eq!` with a literal bool + --> $DIR/bool_assert_comparison.rs:170:5 + | +LL | debug_assert_eq!("requires negation".is_empty(), false); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: replace it with `debug_assert!(..)` + | +LL - debug_assert_eq!("requires negation".is_empty(), false); +LL + debug_assert!(!"requires negation".is_empty()); + | -error: aborting due to 22 previous errors +error: aborting due to 33 previous errors diff --git a/src/tools/clippy/tests/ui/box_default.fixed b/src/tools/clippy/tests/ui/box_default.fixed index 7e9f074fd..59c0baf87 100644 --- a/src/tools/clippy/tests/ui/box_default.fixed +++ b/src/tools/clippy/tests/ui/box_default.fixed @@ -33,6 +33,7 @@ fn main() { let _vec4: Box<_> = Box::<Vec<bool>>::default(); let _more = ret_ty_fn(); call_ty_fn(Box::default()); + issue_10381(); } fn ret_ty_fn() -> Box<bool> { @@ -65,3 +66,20 @@ fn issue_10089() { let _ = Box::<WeirdPathed>::default(); }; } + +fn issue_10381() { + #[derive(Default)] + pub struct Foo {} + pub trait Bar {} + impl Bar for Foo {} + + fn maybe_get_bar(i: u32) -> Option<Box<dyn Bar>> { + if i % 2 == 0 { + Some(Box::<Foo>::default()) + } else { + None + } + } + + assert!(maybe_get_bar(2).is_some()); +} diff --git a/src/tools/clippy/tests/ui/box_default.rs b/src/tools/clippy/tests/ui/box_default.rs index 5c8d0b835..f7d832193 100644 --- a/src/tools/clippy/tests/ui/box_default.rs +++ b/src/tools/clippy/tests/ui/box_default.rs @@ -33,6 +33,7 @@ fn main() { let _vec4: Box<_> = Box::new(Vec::from([false; 0])); let _more = ret_ty_fn(); call_ty_fn(Box::new(u8::default())); + issue_10381(); } fn ret_ty_fn() -> Box<bool> { @@ -65,3 +66,20 @@ fn issue_10089() { let _ = Box::new(WeirdPathed::default()); }; } + +fn issue_10381() { + #[derive(Default)] + pub struct Foo {} + pub trait Bar {} + impl Bar for Foo {} + + fn maybe_get_bar(i: u32) -> Option<Box<dyn Bar>> { + if i % 2 == 0 { + Some(Box::new(Foo::default())) + } else { + None + } + } + + assert!(maybe_get_bar(2).is_some()); +} diff --git a/src/tools/clippy/tests/ui/box_default.stderr b/src/tools/clippy/tests/ui/box_default.stderr index 249eb340f..78e17b9f0 100644 --- a/src/tools/clippy/tests/ui/box_default.stderr +++ b/src/tools/clippy/tests/ui/box_default.stderr @@ -73,22 +73,28 @@ LL | call_ty_fn(Box::new(u8::default())); | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Box::default()` error: `Box::new(_)` of default value - --> $DIR/box_default.rs:39:5 + --> $DIR/box_default.rs:40:5 | LL | Box::new(bool::default()) | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Box::<bool>::default()` error: `Box::new(_)` of default value - --> $DIR/box_default.rs:56:28 + --> $DIR/box_default.rs:57:28 | LL | let _: Box<dyn Read> = Box::new(ImplementsDefault::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Box::<ImplementsDefault>::default()` error: `Box::new(_)` of default value - --> $DIR/box_default.rs:65:17 + --> $DIR/box_default.rs:66:17 | LL | let _ = Box::new(WeirdPathed::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Box::<WeirdPathed>::default()` -error: aborting due to 15 previous errors +error: `Box::new(_)` of default value + --> $DIR/box_default.rs:78:18 + | +LL | Some(Box::new(Foo::default())) + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Box::<Foo>::default()` + +error: aborting due to 16 previous errors diff --git a/src/tools/clippy/tests/ui/bytes_nth.fixed b/src/tools/clippy/tests/ui/bytes_nth.fixed index b1fb2e16b..a35c679af 100644 --- a/src/tools/clippy/tests/ui/bytes_nth.fixed +++ b/src/tools/clippy/tests/ui/bytes_nth.fixed @@ -5,7 +5,7 @@ fn main() { let s = String::from("String"); - let _ = s.as_bytes().get(3); - let _ = &s.as_bytes().get(3); - let _ = s[..].as_bytes().get(3); + let _ = s.as_bytes().get(3).copied(); + let _ = &s.as_bytes()[3]; + let _ = s[..].as_bytes().get(3).copied(); } diff --git a/src/tools/clippy/tests/ui/bytes_nth.rs b/src/tools/clippy/tests/ui/bytes_nth.rs index 034c54e6a..1ecffea53 100644 --- a/src/tools/clippy/tests/ui/bytes_nth.rs +++ b/src/tools/clippy/tests/ui/bytes_nth.rs @@ -6,6 +6,6 @@ fn main() { let s = String::from("String"); let _ = s.bytes().nth(3); - let _ = &s.bytes().nth(3); + let _ = &s.bytes().nth(3).unwrap(); let _ = s[..].bytes().nth(3); } diff --git a/src/tools/clippy/tests/ui/bytes_nth.stderr b/src/tools/clippy/tests/ui/bytes_nth.stderr index 9851d4791..e8b150278 100644 --- a/src/tools/clippy/tests/ui/bytes_nth.stderr +++ b/src/tools/clippy/tests/ui/bytes_nth.stderr @@ -2,21 +2,21 @@ error: called `.bytes().nth()` on a `String` --> $DIR/bytes_nth.rs:8:13 | LL | let _ = s.bytes().nth(3); - | ^^^^^^^^^^^^^^^^ help: try: `s.as_bytes().get(3)` + | ^^^^^^^^^^^^^^^^ help: try: `s.as_bytes().get(3).copied()` | = note: `-D clippy::bytes-nth` implied by `-D warnings` -error: called `.bytes().nth()` on a `String` +error: called `.bytes().nth().unwrap()` on a `String` --> $DIR/bytes_nth.rs:9:14 | -LL | let _ = &s.bytes().nth(3); - | ^^^^^^^^^^^^^^^^ help: try: `s.as_bytes().get(3)` +LL | let _ = &s.bytes().nth(3).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `s.as_bytes()[3]` error: called `.bytes().nth()` on a `str` --> $DIR/bytes_nth.rs:10:13 | LL | let _ = s[..].bytes().nth(3); - | ^^^^^^^^^^^^^^^^^^^^ help: try: `s[..].as_bytes().get(3)` + | ^^^^^^^^^^^^^^^^^^^^ help: try: `s[..].as_bytes().get(3).copied()` error: aborting due to 3 previous errors diff --git a/src/tools/clippy/tests/ui/cast.rs b/src/tools/clippy/tests/ui/cast.rs index e6031e9ad..8b2673c2a 100644 --- a/src/tools/clippy/tests/ui/cast.rs +++ b/src/tools/clippy/tests/ui/cast.rs @@ -28,6 +28,7 @@ fn main() { 1i32 as u8; 1f64 as isize; 1f64 as usize; + 1f32 as u32 as u16; // Test clippy::cast_possible_wrap 1u8 as i8; 1u16 as i16; diff --git a/src/tools/clippy/tests/ui/cast.stderr b/src/tools/clippy/tests/ui/cast.stderr index 0c63b4af3..451078de2 100644 --- a/src/tools/clippy/tests/ui/cast.stderr +++ b/src/tools/clippy/tests/ui/cast.stderr @@ -42,13 +42,24 @@ error: casting `f32` to `i32` may truncate the value LL | 1f32 as i32; | ^^^^^^^^^^^ | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` +help: ... or use `try_from` and handle the error accordingly + | +LL | i32::try_from(1f32); + | ~~~~~~~~~~~~~~~~~~~ error: casting `f32` to `u32` may truncate the value --> $DIR/cast.rs:25:5 | LL | 1f32 as u32; | ^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | u32::try_from(1f32); + | ~~~~~~~~~~~~~~~~~~~ error: casting `f32` to `u32` may lose the sign of the value --> $DIR/cast.rs:25:5 @@ -63,30 +74,60 @@ error: casting `f64` to `f32` may truncate the value | LL | 1f64 as f32; | ^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | f32::try_from(1f64); + | ~~~~~~~~~~~~~~~~~~~ error: casting `i32` to `i8` may truncate the value --> $DIR/cast.rs:27:5 | LL | 1i32 as i8; | ^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | i8::try_from(1i32); + | ~~~~~~~~~~~~~~~~~~ error: casting `i32` to `u8` may truncate the value --> $DIR/cast.rs:28:5 | LL | 1i32 as u8; | ^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | u8::try_from(1i32); + | ~~~~~~~~~~~~~~~~~~ error: casting `f64` to `isize` may truncate the value --> $DIR/cast.rs:29:5 | LL | 1f64 as isize; | ^^^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | isize::try_from(1f64); + | ~~~~~~~~~~~~~~~~~~~~~ error: casting `f64` to `usize` may truncate the value --> $DIR/cast.rs:30:5 | LL | 1f64 as usize; | ^^^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | usize::try_from(1f64); + | ~~~~~~~~~~~~~~~~~~~~~ error: casting `f64` to `usize` may lose the sign of the value --> $DIR/cast.rs:30:5 @@ -94,8 +135,38 @@ error: casting `f64` to `usize` may lose the sign of the value LL | 1f64 as usize; | ^^^^^^^^^^^^^ +error: casting `u32` to `u16` may truncate the value + --> $DIR/cast.rs:31:5 + | +LL | 1f32 as u32 as u16; + | ^^^^^^^^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | u16::try_from(1f32 as u32); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: casting `f32` to `u32` may truncate the value + --> $DIR/cast.rs:31:5 + | +LL | 1f32 as u32 as u16; + | ^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | u32::try_from(1f32) as u16; + | ~~~~~~~~~~~~~~~~~~~ + +error: casting `f32` to `u32` may lose the sign of the value + --> $DIR/cast.rs:31:5 + | +LL | 1f32 as u32 as u16; + | ^^^^^^^^^^^ + error: casting `u8` to `i8` may wrap around the value - --> $DIR/cast.rs:32:5 + --> $DIR/cast.rs:33:5 | LL | 1u8 as i8; | ^^^^^^^^^ @@ -103,61 +174,79 @@ LL | 1u8 as i8; = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` error: casting `u16` to `i16` may wrap around the value - --> $DIR/cast.rs:33:5 + --> $DIR/cast.rs:34:5 | LL | 1u16 as i16; | ^^^^^^^^^^^ error: casting `u32` to `i32` may wrap around the value - --> $DIR/cast.rs:34:5 + --> $DIR/cast.rs:35:5 | LL | 1u32 as i32; | ^^^^^^^^^^^ error: casting `u64` to `i64` may wrap around the value - --> $DIR/cast.rs:35:5 + --> $DIR/cast.rs:36:5 | LL | 1u64 as i64; | ^^^^^^^^^^^ error: casting `usize` to `isize` may wrap around the value - --> $DIR/cast.rs:36:5 + --> $DIR/cast.rs:37:5 | LL | 1usize as isize; | ^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> $DIR/cast.rs:39:5 + --> $DIR/cast.rs:40:5 | LL | -1i32 as u32; | ^^^^^^^^^^^^ error: casting `isize` to `usize` may lose the sign of the value - --> $DIR/cast.rs:41:5 + --> $DIR/cast.rs:42:5 | LL | -1isize as usize; | ^^^^^^^^^^^^^^^^ error: casting `i64` to `i8` may truncate the value - --> $DIR/cast.rs:108:5 + --> $DIR/cast.rs:109:5 | LL | (-99999999999i64).min(1) as i8; // should be linted because signed | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | i8::try_from((-99999999999i64).min(1)); // should be linted because signed + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: casting `u64` to `u8` may truncate the value - --> $DIR/cast.rs:120:5 + --> $DIR/cast.rs:121:5 | LL | 999999u64.clamp(0, 256) as u8; // should still be linted | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | u8::try_from(999999u64.clamp(0, 256)); // should still be linted + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: casting `main::E2` to `u8` may truncate the value - --> $DIR/cast.rs:141:21 + --> $DIR/cast.rs:142:21 | LL | let _ = self as u8; | ^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | let _ = u8::try_from(self); + | ~~~~~~~~~~~~~~~~~~ error: casting `main::E2::B` to `u8` will truncate the value - --> $DIR/cast.rs:142:21 + --> $DIR/cast.rs:143:21 | LL | let _ = Self::B as u8; | ^^^^^^^^^^^^^ @@ -165,46 +254,82 @@ LL | let _ = Self::B as u8; = note: `-D clippy::cast-enum-truncation` implied by `-D warnings` error: casting `main::E5` to `i8` may truncate the value - --> $DIR/cast.rs:178:21 + --> $DIR/cast.rs:179:21 | LL | let _ = self as i8; | ^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | let _ = i8::try_from(self); + | ~~~~~~~~~~~~~~~~~~ error: casting `main::E5::A` to `i8` will truncate the value - --> $DIR/cast.rs:179:21 + --> $DIR/cast.rs:180:21 | LL | let _ = Self::A as i8; | ^^^^^^^^^^^^^ error: casting `main::E6` to `i16` may truncate the value - --> $DIR/cast.rs:193:21 + --> $DIR/cast.rs:194:21 | LL | let _ = self as i16; | ^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | let _ = i16::try_from(self); + | ~~~~~~~~~~~~~~~~~~~ error: casting `main::E7` to `usize` may truncate the value on targets with 32-bit wide pointers - --> $DIR/cast.rs:208:21 + --> $DIR/cast.rs:209:21 | LL | let _ = self as usize; | ^^^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | let _ = usize::try_from(self); + | ~~~~~~~~~~~~~~~~~~~~~ error: casting `main::E10` to `u16` may truncate the value - --> $DIR/cast.rs:249:21 + --> $DIR/cast.rs:250:21 | LL | let _ = self as u16; | ^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | let _ = u16::try_from(self); + | ~~~~~~~~~~~~~~~~~~~ error: casting `u32` to `u8` may truncate the value - --> $DIR/cast.rs:257:13 + --> $DIR/cast.rs:258:13 | LL | let c = (q >> 16) as u8; | ^^^^^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | let c = u8::try_from((q >> 16)); + | ~~~~~~~~~~~~~~~~~~~~~~~ error: casting `u32` to `u8` may truncate the value - --> $DIR/cast.rs:260:13 + --> $DIR/cast.rs:261:13 | LL | let c = (q / 1000) as u8; | ^^^^^^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | let c = u8::try_from((q / 1000)); + | ~~~~~~~~~~~~~~~~~~~~~~~~ -error: aborting due to 33 previous errors +error: aborting due to 36 previous errors diff --git a/src/tools/clippy/tests/ui/cast_size.stderr b/src/tools/clippy/tests/ui/cast_size.stderr index 95552f2e2..6d2d49d9e 100644 --- a/src/tools/clippy/tests/ui/cast_size.stderr +++ b/src/tools/clippy/tests/ui/cast_size.stderr @@ -4,7 +4,12 @@ error: casting `isize` to `i8` may truncate the value LL | 1isize as i8; | ^^^^^^^^^^^^ | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` +help: ... or use `try_from` and handle the error accordingly + | +LL | i8::try_from(1isize); + | ~~~~~~~~~~~~~~~~~~~~ error: casting `isize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`isize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) --> $DIR/cast_size.rs:15:5 @@ -37,24 +42,48 @@ error: casting `isize` to `i32` may truncate the value on targets with 64-bit wi | LL | 1isize as i32; | ^^^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | i32::try_from(1isize); + | ~~~~~~~~~~~~~~~~~~~~~ error: casting `isize` to `u32` may truncate the value on targets with 64-bit wide pointers --> $DIR/cast_size.rs:20:5 | LL | 1isize as u32; | ^^^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | u32::try_from(1isize); + | ~~~~~~~~~~~~~~~~~~~~~ error: casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers --> $DIR/cast_size.rs:21:5 | LL | 1usize as u32; | ^^^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | u32::try_from(1usize); + | ~~~~~~~~~~~~~~~~~~~~~ error: casting `usize` to `i32` may truncate the value on targets with 64-bit wide pointers --> $DIR/cast_size.rs:22:5 | LL | 1usize as i32; | ^^^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | i32::try_from(1usize); + | ~~~~~~~~~~~~~~~~~~~~~ error: casting `usize` to `i32` may wrap around the value on targets with 32-bit wide pointers --> $DIR/cast_size.rs:22:5 @@ -69,18 +98,36 @@ error: casting `i64` to `isize` may truncate the value on targets with 32-bit wi | LL | 1i64 as isize; | ^^^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | isize::try_from(1i64); + | ~~~~~~~~~~~~~~~~~~~~~ error: casting `i64` to `usize` may truncate the value on targets with 32-bit wide pointers --> $DIR/cast_size.rs:25:5 | LL | 1i64 as usize; | ^^^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | usize::try_from(1i64); + | ~~~~~~~~~~~~~~~~~~~~~ error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers --> $DIR/cast_size.rs:26:5 | LL | 1u64 as isize; | ^^^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | isize::try_from(1u64); + | ~~~~~~~~~~~~~~~~~~~~~ error: casting `u64` to `isize` may wrap around the value on targets with 64-bit wide pointers --> $DIR/cast_size.rs:26:5 @@ -93,6 +140,12 @@ error: casting `u64` to `usize` may truncate the value on targets with 32-bit wi | LL | 1u64 as usize; | ^^^^^^^^^^^^^ + | + = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... +help: ... or use `try_from` and handle the error accordingly + | +LL | usize::try_from(1u64); + | ~~~~~~~~~~~~~~~~~~~~~ error: casting `u32` to `isize` may wrap around the value on targets with 32-bit wide pointers --> $DIR/cast_size.rs:28:5 diff --git a/src/tools/clippy/tests/ui/crashes/ice-2774.stderr b/src/tools/clippy/tests/ui/crashes/ice-2774.stderr index 1f26c7f4d..c5ea0b16d 100644 --- a/src/tools/clippy/tests/ui/crashes/ice-2774.stderr +++ b/src/tools/clippy/tests/ui/crashes/ice-2774.stderr @@ -5,6 +5,11 @@ LL | pub fn add_barfoos_to_foos<'a>(bars: &HashSet<&'a Bar>) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::needless-lifetimes` implied by `-D warnings` +help: elide the lifetimes + | +LL - pub fn add_barfoos_to_foos<'a>(bars: &HashSet<&'a Bar>) { +LL + pub fn add_barfoos_to_foos(bars: &HashSet<&Bar>) { + | error: aborting due to previous error diff --git a/src/tools/clippy/tests/ui/crashes/ice-rust-107877.rs b/src/tools/clippy/tests/ui/crashes/ice-rust-107877.rs new file mode 100644 index 000000000..7f5bae60d --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/ice-rust-107877.rs @@ -0,0 +1,17 @@ +#![allow(dead_code)] + +struct Foo; + +impl<'a> std::convert::TryFrom<&'a String> for Foo { + type Error = std::convert::Infallible; + + fn try_from(_: &'a String) -> Result<Self, Self::Error> { + Ok(Foo) + } +} + +fn find<E>(_: impl std::convert::TryInto<Foo, Error = E>) {} + +fn main() { + find(&String::new()); +} diff --git a/src/tools/clippy/tests/ui/crashes/needless_lifetimes_impl_trait.stderr b/src/tools/clippy/tests/ui/crashes/needless_lifetimes_impl_trait.stderr index 875d5ab4f..0b0e0ad26 100644 --- a/src/tools/clippy/tests/ui/crashes/needless_lifetimes_impl_trait.stderr +++ b/src/tools/clippy/tests/ui/crashes/needless_lifetimes_impl_trait.stderr @@ -9,6 +9,11 @@ note: the lint level is defined here | LL | #![deny(clippy::needless_lifetimes)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: elide the lifetimes + | +LL - fn baz<'a>(&'a self) -> impl Foo + 'a { +LL + fn baz(&self) -> impl Foo + '_ { + | error: aborting due to previous error diff --git a/src/tools/clippy/tests/ui/crashes/needless_pass_by_value-w-late-bound.rs b/src/tools/clippy/tests/ui/crashes/needless_pass_by_value-w-late-bound.rs new file mode 100644 index 000000000..dd3d8b8b6 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/needless_pass_by_value-w-late-bound.rs @@ -0,0 +1,9 @@ +// https://github.com/rust-lang/rust/issues/107147 + +#![warn(clippy::needless_pass_by_value)] + +struct Foo<'a>(&'a [(); 100]); + +fn test(x: Foo<'_>) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/crashes/needless_pass_by_value-w-late-bound.stderr b/src/tools/clippy/tests/ui/crashes/needless_pass_by_value-w-late-bound.stderr new file mode 100644 index 000000000..7a0a64897 --- /dev/null +++ b/src/tools/clippy/tests/ui/crashes/needless_pass_by_value-w-late-bound.stderr @@ -0,0 +1,15 @@ +error: this argument is passed by value, but not consumed in the function body + --> $DIR/needless_pass_by_value-w-late-bound.rs:7:12 + | +LL | fn test(x: Foo<'_>) {} + | ^^^^^^^ help: consider taking a reference instead: `&Foo<'_>` + | +help: consider marking this type as `Copy` + --> $DIR/needless_pass_by_value-w-late-bound.rs:5:1 + | +LL | struct Foo<'a>(&'a [(); 100]); + | ^^^^^^^^^^^^^^ + = note: `-D clippy::needless-pass-by-value` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/doc/doc-fixable.fixed b/src/tools/clippy/tests/ui/doc/doc-fixable.fixed index 747801b40..ecb0bf364 100644 --- a/src/tools/clippy/tests/ui/doc/doc-fixable.fixed +++ b/src/tools/clippy/tests/ui/doc/doc-fixable.fixed @@ -78,7 +78,7 @@ fn test_allowed() { /// This test has [a `link_with_underscores`][chunked-example] inside it. See #823. /// See also [the issue tracker](https://github.com/rust-lang/rust-clippy/search?q=clippy::doc_markdown&type=Issues) /// on GitHub (which is a camel-cased word, but is OK). And here is another [inline link][inline_link]. -/// It can also be [`inline_link2`]. +/// It can also be [inline_link2]. A link to [StackOverflow](https://stackoverflow.com) is also acceptable. /// /// [chunked-example]: https://en.wikipedia.org/wiki/Chunked_transfer_encoding#Example /// [inline_link]: https://foobar diff --git a/src/tools/clippy/tests/ui/doc/doc-fixable.rs b/src/tools/clippy/tests/ui/doc/doc-fixable.rs index f3cf96615..11c48dd10 100644 --- a/src/tools/clippy/tests/ui/doc/doc-fixable.rs +++ b/src/tools/clippy/tests/ui/doc/doc-fixable.rs @@ -75,10 +75,10 @@ fn test_units() { fn test_allowed() { } -/// This test has [a link_with_underscores][chunked-example] inside it. See #823. +/// This test has [a `link_with_underscores`][chunked-example] inside it. See #823. /// See also [the issue tracker](https://github.com/rust-lang/rust-clippy/search?q=clippy::doc_markdown&type=Issues) /// on GitHub (which is a camel-cased word, but is OK). And here is another [inline link][inline_link]. -/// It can also be [inline_link2]. +/// It can also be [inline_link2]. A link to [StackOverflow](https://stackoverflow.com) is also acceptable. /// /// [chunked-example]: https://en.wikipedia.org/wiki/Chunked_transfer_encoding#Example /// [inline_link]: https://foobar diff --git a/src/tools/clippy/tests/ui/doc/doc-fixable.stderr b/src/tools/clippy/tests/ui/doc/doc-fixable.stderr index 40345370c..6c67c903c 100644 --- a/src/tools/clippy/tests/ui/doc/doc-fixable.stderr +++ b/src/tools/clippy/tests/ui/doc/doc-fixable.stderr @@ -143,28 +143,6 @@ LL | /// `be_sure_we_got_to_the_end_of_it` | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> $DIR/doc-fixable.rs:78:22 - | -LL | /// This test has [a link_with_underscores][chunked-example] inside it. See #823. - | ^^^^^^^^^^^^^^^^^^^^^ - | -help: try - | -LL | /// This test has [a `link_with_underscores`][chunked-example] inside it. See #823. - | ~~~~~~~~~~~~~~~~~~~~~~~ - -error: item in documentation is missing backticks - --> $DIR/doc-fixable.rs:81:21 - | -LL | /// It can also be [inline_link2]. - | ^^^^^^^^^^^^ - | -help: try - | -LL | /// It can also be [`inline_link2`]. - | ~~~~~~~~~~~~~~ - -error: item in documentation is missing backticks --> $DIR/doc-fixable.rs:91:5 | LL | /// be_sure_we_got_to_the_end_of_it @@ -329,5 +307,5 @@ help: try LL | /// An iterator over `mycrate::Collection`'s values. | ~~~~~~~~~~~~~~~~~~~~~ -error: aborting due to 30 previous errors +error: aborting due to 28 previous errors diff --git a/src/tools/clippy/tests/ui/entry.fixed b/src/tools/clippy/tests/ui/entry.fixed index 79c29c04e..dbe09e0ff 100644 --- a/src/tools/clippy/tests/ui/entry.fixed +++ b/src/tools/clippy/tests/ui/entry.fixed @@ -152,4 +152,18 @@ fn hash_map<K: Eq + Hash + Copy, V: Copy>(m: &mut HashMap<K, V>, m2: &mut HashMa }); } +// Issue 10331 +// do not suggest a bad expansion because the compiler unrolls the first +// occurrence of the loop +pub fn issue_10331() { + let mut m = HashMap::new(); + let mut i = 0; + let mut x = 0; + while !m.contains_key(&x) { + m.insert(x, i); + i += 1; + x += 1; + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/entry.rs b/src/tools/clippy/tests/ui/entry.rs index 2d7985457..30fed34fc 100644 --- a/src/tools/clippy/tests/ui/entry.rs +++ b/src/tools/clippy/tests/ui/entry.rs @@ -156,4 +156,18 @@ fn hash_map<K: Eq + Hash + Copy, V: Copy>(m: &mut HashMap<K, V>, m2: &mut HashMa } } +// Issue 10331 +// do not suggest a bad expansion because the compiler unrolls the first +// occurrence of the loop +pub fn issue_10331() { + let mut m = HashMap::new(); + let mut i = 0; + let mut x = 0; + while !m.contains_key(&x) { + m.insert(x, i); + i += 1; + x += 1; + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/explicit_auto_deref.fixed b/src/tools/clippy/tests/ui/explicit_auto_deref.fixed index 475fae5e8..5d40c8504 100644 --- a/src/tools/clippy/tests/ui/explicit_auto_deref.fixed +++ b/src/tools/clippy/tests/ui/explicit_auto_deref.fixed @@ -269,6 +269,9 @@ fn main() { trait WithAssoc { type Assoc: ?Sized; + fn to_assoc(&self) -> &Self::Assoc { + panic!() + } } impl WithAssoc for String { type Assoc = str; @@ -281,4 +284,15 @@ fn main() { // Issue #9901 fn takes_ref(_: &i32) {} takes_ref(*Box::new(&0i32)); + + // Issue #10384 + impl<'a> WithAssoc for &'a u32 { + type Assoc = dyn core::fmt::Display; + fn to_assoc(&self) -> &Self::Assoc { + *self + } + } + fn return_dyn_assoc<'a>(x: &'a &'a u32) -> &'a <&'a u32 as WithAssoc>::Assoc { + *x + } } diff --git a/src/tools/clippy/tests/ui/explicit_auto_deref.rs b/src/tools/clippy/tests/ui/explicit_auto_deref.rs index c1894258f..79e03f4d7 100644 --- a/src/tools/clippy/tests/ui/explicit_auto_deref.rs +++ b/src/tools/clippy/tests/ui/explicit_auto_deref.rs @@ -269,6 +269,9 @@ fn main() { trait WithAssoc { type Assoc: ?Sized; + fn to_assoc(&self) -> &Self::Assoc { + panic!() + } } impl WithAssoc for String { type Assoc = str; @@ -281,4 +284,15 @@ fn main() { // Issue #9901 fn takes_ref(_: &i32) {} takes_ref(*Box::new(&0i32)); + + // Issue #10384 + impl<'a> WithAssoc for &'a u32 { + type Assoc = dyn core::fmt::Display; + fn to_assoc(&self) -> &Self::Assoc { + *self + } + } + fn return_dyn_assoc<'a>(x: &'a &'a u32) -> &'a <&'a u32 as WithAssoc>::Assoc { + *x + } } diff --git a/src/tools/clippy/tests/ui/extra_unused_type_parameters.rs b/src/tools/clippy/tests/ui/extra_unused_type_parameters.rs new file mode 100644 index 000000000..480174342 --- /dev/null +++ b/src/tools/clippy/tests/ui/extra_unused_type_parameters.rs @@ -0,0 +1,110 @@ +#![allow(unused, clippy::needless_lifetimes)] +#![warn(clippy::extra_unused_type_parameters)] + +fn unused_ty<T>(x: u8) { + unimplemented!() +} + +fn unused_multi<T, U>(x: u8) { + unimplemented!() +} + +fn unused_with_lt<'a, T>(x: &'a u8) { + unimplemented!() +} + +fn used_ty<T>(x: T, y: u8) {} + +fn used_ref<'a, T>(x: &'a T) {} + +fn used_ret<T: Default>(x: u8) -> T { + T::default() +} + +fn unused_bounded<T: Default, U>(x: U) { + unimplemented!(); +} + +fn unused_where_clause<T, U>(x: U) +where + T: Default, +{ + unimplemented!(); +} + +fn some_unused<A, B, C, D: Iterator<Item = (B, C)>, E>(b: B, c: C) { + unimplemented!(); +} + +fn used_opaque<A>(iter: impl Iterator<Item = A>) -> usize { + iter.count() +} + +fn used_ret_opaque<A>() -> impl Iterator<Item = A> { + std::iter::empty() +} + +fn used_vec_box<T>(x: Vec<Box<T>>) {} + +fn used_body<T: Default + ToString>() -> String { + T::default().to_string() +} + +fn used_closure<T: Default + ToString>() -> impl Fn() { + || println!("{}", T::default().to_string()) +} + +struct S; + +impl S { + fn unused_ty_impl<T>(&self) { + unimplemented!() + } +} + +// Don't lint on trait methods +trait Foo { + fn bar<T>(&self); +} + +impl Foo for S { + fn bar<T>(&self) {} +} + +fn skip_index<A, Iter>(iter: Iter, index: usize) -> impl Iterator<Item = A> +where + Iter: Iterator<Item = A>, +{ + iter.enumerate() + .filter_map(move |(i, a)| if i == index { None } else { Some(a) }) +} + +fn unused_opaque<A, B>(dummy: impl Default) { + unimplemented!() +} + +mod unexported_trait_bounds { + mod private { + pub trait Private {} + } + + fn priv_trait_bound<T: private::Private>() { + unimplemented!(); + } + + fn unused_with_priv_trait_bound<T: private::Private, U>() { + unimplemented!(); + } +} + +mod issue10319 { + fn assert_send<T: Send>() {} + + fn assert_send_where<T>() + where + T: Send, + { + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/extra_unused_type_parameters.stderr b/src/tools/clippy/tests/ui/extra_unused_type_parameters.stderr new file mode 100644 index 000000000..86c88fc9b --- /dev/null +++ b/src/tools/clippy/tests/ui/extra_unused_type_parameters.stderr @@ -0,0 +1,75 @@ +error: type parameter goes unused in function definition + --> $DIR/extra_unused_type_parameters.rs:4:13 + | +LL | fn unused_ty<T>(x: u8) { + | ^^^ + | + = help: consider removing the parameter + = note: `-D clippy::extra-unused-type-parameters` implied by `-D warnings` + +error: type parameters go unused in function definition + --> $DIR/extra_unused_type_parameters.rs:8:16 + | +LL | fn unused_multi<T, U>(x: u8) { + | ^^^^^^ + | + = help: consider removing the parameters + +error: type parameter goes unused in function definition + --> $DIR/extra_unused_type_parameters.rs:12:23 + | +LL | fn unused_with_lt<'a, T>(x: &'a u8) { + | ^ + | + = help: consider removing the parameter + +error: type parameter goes unused in function definition + --> $DIR/extra_unused_type_parameters.rs:24:19 + | +LL | fn unused_bounded<T: Default, U>(x: U) { + | ^^^^^^^^^^^ + | + = help: consider removing the parameter + +error: type parameter goes unused in function definition + --> $DIR/extra_unused_type_parameters.rs:28:24 + | +LL | fn unused_where_clause<T, U>(x: U) + | ^^ + | + = help: consider removing the parameter + +error: type parameters go unused in function definition + --> $DIR/extra_unused_type_parameters.rs:35:16 + | +LL | fn some_unused<A, B, C, D: Iterator<Item = (B, C)>, E>(b: B, c: C) { + | ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ + | + = help: consider removing the parameters + +error: type parameter goes unused in function definition + --> $DIR/extra_unused_type_parameters.rs:60:22 + | +LL | fn unused_ty_impl<T>(&self) { + | ^^^ + | + = help: consider removing the parameter + +error: type parameters go unused in function definition + --> $DIR/extra_unused_type_parameters.rs:82:17 + | +LL | fn unused_opaque<A, B>(dummy: impl Default) { + | ^^^^^^ + | + = help: consider removing the parameters + +error: type parameter goes unused in function definition + --> $DIR/extra_unused_type_parameters.rs:95:58 + | +LL | fn unused_with_priv_trait_bound<T: private::Private, U>() { + | ^ + | + = help: consider removing the parameter + +error: aborting due to 9 previous errors + diff --git a/src/tools/clippy/tests/ui/format.fixed b/src/tools/clippy/tests/ui/format.fixed index beedf2c1d..cd2f70ee8 100644 --- a/src/tools/clippy/tests/ui/format.fixed +++ b/src/tools/clippy/tests/ui/format.fixed @@ -1,4 +1,5 @@ // run-rustfix +// aux-build: proc_macro_with_span.rs #![warn(clippy::useless_format)] #![allow( unused_tuple_struct_fields, @@ -9,6 +10,8 @@ clippy::uninlined_format_args )] +extern crate proc_macro_with_span; + struct Foo(pub String); macro_rules! foo { @@ -87,4 +90,7 @@ fn main() { let _ = abc.to_string(); let xx = "xx"; let _ = xx.to_string(); + + // Issue #10148 + println!(proc_macro_with_span::with_span!(""something "")); } diff --git a/src/tools/clippy/tests/ui/format.rs b/src/tools/clippy/tests/ui/format.rs index e805f1818..c22345a79 100644 --- a/src/tools/clippy/tests/ui/format.rs +++ b/src/tools/clippy/tests/ui/format.rs @@ -1,4 +1,5 @@ // run-rustfix +// aux-build: proc_macro_with_span.rs #![warn(clippy::useless_format)] #![allow( unused_tuple_struct_fields, @@ -9,6 +10,8 @@ clippy::uninlined_format_args )] +extern crate proc_macro_with_span; + struct Foo(pub String); macro_rules! foo { @@ -89,4 +92,7 @@ fn main() { let _ = format!("{abc}"); let xx = "xx"; let _ = format!("{xx}"); + + // Issue #10148 + println!(proc_macro_with_span::with_span!(""something "")); } diff --git a/src/tools/clippy/tests/ui/format.stderr b/src/tools/clippy/tests/ui/format.stderr index 0ef0ac655..a0e5d5c8a 100644 --- a/src/tools/clippy/tests/ui/format.stderr +++ b/src/tools/clippy/tests/ui/format.stderr @@ -1,5 +1,5 @@ error: useless use of `format!` - --> $DIR/format.rs:19:5 + --> $DIR/format.rs:22:5 | LL | format!("foo"); | ^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string()` @@ -7,19 +7,19 @@ LL | format!("foo"); = note: `-D clippy::useless-format` implied by `-D warnings` error: useless use of `format!` - --> $DIR/format.rs:20:5 + --> $DIR/format.rs:23:5 | LL | format!("{{}}"); | ^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"{}".to_string()` error: useless use of `format!` - --> $DIR/format.rs:21:5 + --> $DIR/format.rs:24:5 | LL | format!("{{}} abc {{}}"); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"{} abc {}".to_string()` error: useless use of `format!` - --> $DIR/format.rs:22:5 + --> $DIR/format.rs:25:5 | LL | / format!( LL | | r##"foo {{}} @@ -34,67 +34,67 @@ LL ~ " bar"##.to_string(); | error: useless use of `format!` - --> $DIR/format.rs:27:13 + --> $DIR/format.rs:30:13 | LL | let _ = format!(""); | ^^^^^^^^^^^ help: consider using `String::new()`: `String::new()` error: useless use of `format!` - --> $DIR/format.rs:29:5 + --> $DIR/format.rs:32:5 | LL | format!("{}", "foo"); | ^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string()` error: useless use of `format!` - --> $DIR/format.rs:37:5 + --> $DIR/format.rs:40:5 | LL | format!("{}", arg); | ^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `arg.to_string()` error: useless use of `format!` - --> $DIR/format.rs:67:5 + --> $DIR/format.rs:70:5 | LL | format!("{}", 42.to_string()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `42.to_string()` error: useless use of `format!` - --> $DIR/format.rs:69:5 + --> $DIR/format.rs:72:5 | LL | format!("{}", x.display().to_string()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.display().to_string()` error: useless use of `format!` - --> $DIR/format.rs:73:18 + --> $DIR/format.rs:76:18 | LL | let _ = Some(format!("{}", a + "bar")); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `a + "bar"` error: useless use of `format!` - --> $DIR/format.rs:77:22 + --> $DIR/format.rs:80:22 | LL | let _s: String = format!("{}", &*v.join("/n")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `(&*v.join("/n")).to_string()` error: useless use of `format!` - --> $DIR/format.rs:83:13 + --> $DIR/format.rs:86:13 | LL | let _ = format!("{x}"); | ^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.to_string()` error: useless use of `format!` - --> $DIR/format.rs:85:13 + --> $DIR/format.rs:88:13 | LL | let _ = format!("{y}", y = x); | ^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.to_string()` error: useless use of `format!` - --> $DIR/format.rs:89:13 + --> $DIR/format.rs:92:13 | LL | let _ = format!("{abc}"); | ^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `abc.to_string()` error: useless use of `format!` - --> $DIR/format.rs:91:13 + --> $DIR/format.rs:94:13 | LL | let _ = format!("{xx}"); | ^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `xx.to_string()` diff --git a/src/tools/clippy/tests/ui/impl_trait_in_params.rs b/src/tools/clippy/tests/ui/impl_trait_in_params.rs new file mode 100644 index 000000000..07560101a --- /dev/null +++ b/src/tools/clippy/tests/ui/impl_trait_in_params.rs @@ -0,0 +1,17 @@ +#![allow(unused)] +#![warn(clippy::impl_trait_in_params)] + +pub trait Trait {} +pub trait AnotherTrait<T> {} + +// Should warn +pub fn a(_: impl Trait) {} +pub fn c<C: Trait>(_: C, _: impl Trait) {} +fn d(_: impl AnotherTrait<u32>) {} + +// Shouldn't warn + +pub fn b<B: Trait>(_: B) {} +fn e<T: AnotherTrait<u32>>(_: T) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/impl_trait_in_params.stderr b/src/tools/clippy/tests/ui/impl_trait_in_params.stderr new file mode 100644 index 000000000..acfcc2144 --- /dev/null +++ b/src/tools/clippy/tests/ui/impl_trait_in_params.stderr @@ -0,0 +1,25 @@ +error: '`impl Trait` used as a function parameter' + --> $DIR/impl_trait_in_params.rs:8:13 + | +LL | pub fn a(_: impl Trait) {} + | ^^^^^^^^^^ + | + = note: `-D clippy::impl-trait-in-params` implied by `-D warnings` +help: add a type paremeter + | +LL | pub fn a<{ /* Generic name */ }: Trait>(_: impl Trait) {} + | +++++++++++++++++++++++++++++++ + +error: '`impl Trait` used as a function parameter' + --> $DIR/impl_trait_in_params.rs:9:29 + | +LL | pub fn c<C: Trait>(_: C, _: impl Trait) {} + | ^^^^^^^^^^ + | +help: add a type paremeter + | +LL | pub fn c<C: Trait, { /* Generic name */ }: Trait>(_: C, _: impl Trait) {} + | +++++++++++++++++++++++++++++++ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/large_digit_groups.fixed b/src/tools/clippy/tests/ui/large_digit_groups.fixed index 3430c137e..ea18dac06 100644 --- a/src/tools/clippy/tests/ui/large_digit_groups.fixed +++ b/src/tools/clippy/tests/ui/large_digit_groups.fixed @@ -11,7 +11,7 @@ fn main() { let _good = ( 0b1011_i64, 0o1_234_u32, - 0x0123_4567, + 0x1_234_567, 1_2345_6789, 1234_f32, 1_234.12_f32, @@ -19,7 +19,7 @@ fn main() { 1.123_4_f32, ); let _bad = ( - 0b11_0110_i64, + 0b1_10110_i64, 0xdead_beef_usize, 123_456_f32, 123_456.12_f32, diff --git a/src/tools/clippy/tests/ui/large_digit_groups.stderr b/src/tools/clippy/tests/ui/large_digit_groups.stderr index 13d108b56..19c0fae98 100644 --- a/src/tools/clippy/tests/ui/large_digit_groups.stderr +++ b/src/tools/clippy/tests/ui/large_digit_groups.stderr @@ -1,22 +1,10 @@ -error: digits of hex or binary literal not grouped by four - --> $DIR/large_digit_groups.rs:14:9 - | -LL | 0x1_234_567, - | ^^^^^^^^^^^ help: consider: `0x0123_4567` - | - = note: `-D clippy::unusual-byte-groupings` implied by `-D warnings` - -error: digits of hex or binary literal not grouped by four - --> $DIR/large_digit_groups.rs:22:9 - | -LL | 0b1_10110_i64, - | ^^^^^^^^^^^^^ help: consider: `0b11_0110_i64` - -error: digits of hex or binary literal not grouped by four +error: digits of hex, binary or octal literal not in groups of equal size --> $DIR/large_digit_groups.rs:23:9 | LL | 0xd_e_adbee_f_usize, | ^^^^^^^^^^^^^^^^^^^ help: consider: `0xdead_beef_usize` + | + = note: `-D clippy::unusual-byte-groupings` implied by `-D warnings` error: digit groups should be smaller --> $DIR/large_digit_groups.rs:24:9 @@ -44,5 +32,5 @@ error: digit groups should be smaller LL | 1_23456.12345_6_f64, | ^^^^^^^^^^^^^^^^^^^ help: consider: `123_456.123_456_f64` -error: aborting due to 7 previous errors +error: aborting due to 5 previous errors diff --git a/src/tools/clippy/tests/ui/len_without_is_empty.rs b/src/tools/clippy/tests/ui/len_without_is_empty.rs index 78397c2af..b5dec6c46 100644 --- a/src/tools/clippy/tests/ui/len_without_is_empty.rs +++ b/src/tools/clippy/tests/ui/len_without_is_empty.rs @@ -282,4 +282,50 @@ impl AsyncLen { } } +// issue #9520 +pub struct NonStandardLenAndIsEmptySignature; +impl NonStandardLenAndIsEmptySignature { + // don't lint + pub fn len(&self, something: usize) -> usize { + something + } + + pub fn is_empty(&self, something: usize) -> bool { + something == 0 + } +} + +// test case for #9520 with generics in the function signature +pub trait TestResource { + type NonStandardSignatureWithGenerics: Copy; + fn lookup_content(&self, item: Self::NonStandardSignatureWithGenerics) -> Result<Option<&[u8]>, String>; +} +pub struct NonStandardSignatureWithGenerics(u32); +impl NonStandardSignatureWithGenerics { + pub fn is_empty<T, U>(self, resource: &T) -> bool + where + T: TestResource<NonStandardSignatureWithGenerics = U>, + U: Copy + From<NonStandardSignatureWithGenerics>, + { + if let Ok(Some(content)) = resource.lookup_content(self.into()) { + content.is_empty() + } else { + true + } + } + + // test case for #9520 with generics in the function signature + pub fn len<T, U>(self, resource: &T) -> usize + where + T: TestResource<NonStandardSignatureWithGenerics = U>, + U: Copy + From<NonStandardSignatureWithGenerics>, + { + if let Ok(Some(content)) = resource.lookup_content(self.into()) { + content.len() + } else { + 0_usize + } + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/let_underscore_untyped.rs b/src/tools/clippy/tests/ui/let_underscore_untyped.rs new file mode 100644 index 000000000..bcb33c5c7 --- /dev/null +++ b/src/tools/clippy/tests/ui/let_underscore_untyped.rs @@ -0,0 +1,54 @@ +#![allow(unused)] +#![warn(clippy::let_underscore_untyped)] + +use std::future::Future; +use std::{boxed::Box, fmt::Display}; + +fn a() -> u32 { + 1 +} + +fn b<T>(x: T) -> T { + x +} + +fn c() -> impl Display { + 1 +} + +fn d(x: &u32) -> &u32 { + x +} + +fn e() -> Result<u32, ()> { + Ok(1) +} + +fn f() -> Box<dyn Display> { + Box::new(1) +} + +fn main() { + let _ = a(); + let _ = b(1); + let _ = c(); + let _ = d(&1); + let _ = e(); + let _ = f(); + + _ = a(); + _ = b(1); + _ = c(); + _ = d(&1); + _ = e(); + _ = f(); + + let _: u32 = a(); + let _: u32 = b(1); + let _: &u32 = d(&1); + let _: Result<_, _> = e(); + let _: Box<_> = f(); + + #[allow(clippy::let_underscore_untyped)] + let _ = a(); +} diff --git a/src/tools/clippy/tests/ui/let_underscore_untyped.stderr b/src/tools/clippy/tests/ui/let_underscore_untyped.stderr new file mode 100644 index 000000000..36c3d1214 --- /dev/null +++ b/src/tools/clippy/tests/ui/let_underscore_untyped.stderr @@ -0,0 +1,51 @@ +error: non-binding `let` without a type annotation + --> $DIR/let_underscore_untyped.rs:32:5 + | +LL | let _ = a(); + | ^^^^^^^^^^^^ + | + = help: consider adding a type annotation or removing the `let` keyword + = note: `-D clippy::let-underscore-untyped` implied by `-D warnings` + +error: non-binding `let` without a type annotation + --> $DIR/let_underscore_untyped.rs:33:5 + | +LL | let _ = b(1); + | ^^^^^^^^^^^^^ + | + = help: consider adding a type annotation or removing the `let` keyword + +error: non-binding `let` without a type annotation + --> $DIR/let_underscore_untyped.rs:34:5 + | +LL | let _ = c(); + | ^^^^^^^^^^^^ + | + = help: consider adding a type annotation or removing the `let` keyword + +error: non-binding `let` without a type annotation + --> $DIR/let_underscore_untyped.rs:35:5 + | +LL | let _ = d(&1); + | ^^^^^^^^^^^^^^ + | + = help: consider adding a type annotation or removing the `let` keyword + +error: non-binding `let` without a type annotation + --> $DIR/let_underscore_untyped.rs:36:5 + | +LL | let _ = e(); + | ^^^^^^^^^^^^ + | + = help: consider adding a type annotation or removing the `let` keyword + +error: non-binding `let` without a type annotation + --> $DIR/let_underscore_untyped.rs:37:5 + | +LL | let _ = f(); + | ^^^^^^^^^^^^ + | + = help: consider adding a type annotation or removing the `let` keyword + +error: aborting due to 6 previous errors + diff --git a/src/tools/clippy/tests/ui/literals.stderr b/src/tools/clippy/tests/ui/literals.stderr index 603d47bac..9bc7948c7 100644 --- a/src/tools/clippy/tests/ui/literals.stderr +++ b/src/tools/clippy/tests/ui/literals.stderr @@ -121,7 +121,7 @@ error: digits grouped inconsistently by underscores LL | let fail23 = 3__16___23; | ^^^^^^^^^^ help: consider: `31_623` -error: digits of hex or binary literal not grouped by four +error: digits of hex, binary or octal literal not in groups of equal size --> $DIR/literals.rs:38:18 | LL | let fail24 = 0xAB_ABC_AB; @@ -129,12 +129,6 @@ LL | let fail24 = 0xAB_ABC_AB; | = note: `-D clippy::unusual-byte-groupings` implied by `-D warnings` -error: digits of hex or binary literal not grouped by four - --> $DIR/literals.rs:39:18 - | -LL | let fail25 = 0b01_100_101; - | ^^^^^^^^^^^^ help: consider: `0b0110_0101` - error: this is a decimal constant --> $DIR/literals.rs:46:13 | @@ -168,5 +162,5 @@ help: if you mean to use a decimal constant, remove the `0` to avoid confusion LL | let _ = 89; | ~~ -error: aborting due to 21 previous errors +error: aborting due to 20 previous errors diff --git a/src/tools/clippy/tests/ui/manual_assert.edition2018.fixed b/src/tools/clippy/tests/ui/manual_assert.edition2018.fixed index 638320dd6..8c7e919bf 100644 --- a/src/tools/clippy/tests/ui/manual_assert.edition2018.fixed +++ b/src/tools/clippy/tests/ui/manual_assert.edition2018.fixed @@ -29,9 +29,7 @@ fn main() { panic!("qaqaq{:?}", a); } assert!(a.is_empty(), "qaqaq{:?}", a); - if !a.is_empty() { - panic!("qwqwq"); - } + assert!(a.is_empty(), "qwqwq"); if a.len() == 3 { println!("qwq"); println!("qwq"); @@ -46,21 +44,11 @@ fn main() { println!("qwq"); } let b = vec![1, 2, 3]; - if b.is_empty() { - panic!("panic1"); - } - if b.is_empty() && a.is_empty() { - panic!("panic2"); - } - if a.is_empty() && !b.is_empty() { - panic!("panic3"); - } - if b.is_empty() || a.is_empty() { - panic!("panic4"); - } - if a.is_empty() || !b.is_empty() { - panic!("panic5"); - } + assert!(!b.is_empty(), "panic1"); + assert!(!(b.is_empty() && a.is_empty()), "panic2"); + assert!(!(a.is_empty() && !b.is_empty()), "panic3"); + assert!(!(b.is_empty() || a.is_empty()), "panic4"); + assert!(!(a.is_empty() || !b.is_empty()), "panic5"); assert!(!a.is_empty(), "with expansion {}", one!()); if a.is_empty() { let _ = 0; @@ -71,12 +59,11 @@ fn main() { fn issue7730(a: u8) { // Suggestion should preserve comment - if a > 2 { - // comment - /* this is a + // comment +/* this is a multiline comment */ - /// Doc comment - panic!("panic with comment") // comment after `panic!` - } +/// Doc comment +// comment after `panic!` +assert!(!(a > 2), "panic with comment"); } diff --git a/src/tools/clippy/tests/ui/manual_assert.edition2018.stderr b/src/tools/clippy/tests/ui/manual_assert.edition2018.stderr index 1f2e1e308..3555ac292 100644 --- a/src/tools/clippy/tests/ui/manual_assert.edition2018.stderr +++ b/src/tools/clippy/tests/ui/manual_assert.edition2018.stderr @@ -9,6 +9,54 @@ LL | | } = note: `-D clippy::manual-assert` implied by `-D warnings` error: only a `panic!` in `if`-then statement + --> $DIR/manual_assert.rs:34:5 + | +LL | / if !a.is_empty() { +LL | | panic!("qwqwq"); +LL | | } + | |_____^ help: try instead: `assert!(a.is_empty(), "qwqwq");` + +error: only a `panic!` in `if`-then statement + --> $DIR/manual_assert.rs:51:5 + | +LL | / if b.is_empty() { +LL | | panic!("panic1"); +LL | | } + | |_____^ help: try instead: `assert!(!b.is_empty(), "panic1");` + +error: only a `panic!` in `if`-then statement + --> $DIR/manual_assert.rs:54:5 + | +LL | / if b.is_empty() && a.is_empty() { +LL | | panic!("panic2"); +LL | | } + | |_____^ help: try instead: `assert!(!(b.is_empty() && a.is_empty()), "panic2");` + +error: only a `panic!` in `if`-then statement + --> $DIR/manual_assert.rs:57:5 + | +LL | / if a.is_empty() && !b.is_empty() { +LL | | panic!("panic3"); +LL | | } + | |_____^ help: try instead: `assert!(!(a.is_empty() && !b.is_empty()), "panic3");` + +error: only a `panic!` in `if`-then statement + --> $DIR/manual_assert.rs:60:5 + | +LL | / if b.is_empty() || a.is_empty() { +LL | | panic!("panic4"); +LL | | } + | |_____^ help: try instead: `assert!(!(b.is_empty() || a.is_empty()), "panic4");` + +error: only a `panic!` in `if`-then statement + --> $DIR/manual_assert.rs:63:5 + | +LL | / if a.is_empty() || !b.is_empty() { +LL | | panic!("panic5"); +LL | | } + | |_____^ help: try instead: `assert!(!(a.is_empty() || !b.is_empty()), "panic5");` + +error: only a `panic!` in `if`-then statement --> $DIR/manual_assert.rs:66:5 | LL | / if a.is_empty() { @@ -16,5 +64,22 @@ LL | | panic!("with expansion {}", one!()) LL | | } | |_____^ help: try instead: `assert!(!a.is_empty(), "with expansion {}", one!());` -error: aborting due to 2 previous errors +error: only a `panic!` in `if`-then statement + --> $DIR/manual_assert.rs:78:5 + | +LL | / if a > 2 { +LL | | // comment +LL | | /* this is a +LL | | multiline +... | +LL | | panic!("panic with comment") // comment after `panic!` +LL | | } + | |_____^ + | +help: try instead + | +LL | assert!(!(a > 2), "panic with comment"); + | + +error: aborting due to 9 previous errors diff --git a/src/tools/clippy/tests/ui/manual_let_else.rs b/src/tools/clippy/tests/ui/manual_let_else.rs index 48a162c13..d175597a4 100644 --- a/src/tools/clippy/tests/ui/manual_let_else.rs +++ b/src/tools/clippy/tests/ui/manual_let_else.rs @@ -248,4 +248,15 @@ fn not_fire() { Some(value) => value, _ => macro_call!(), }; + + // Issue 10296 + // The let/else block in the else part is not divergent despite the presence of return + let _x = if let Some(x) = Some(1) { + x + } else { + let Some(_z) = Some(3) else { + return + }; + 1 + }; } diff --git a/src/tools/clippy/tests/ui/manual_let_else_match.rs b/src/tools/clippy/tests/ui/manual_let_else_match.rs index 28caed9d7..73b746791 100644 --- a/src/tools/clippy/tests/ui/manual_let_else_match.rs +++ b/src/tools/clippy/tests/ui/manual_let_else_match.rs @@ -42,13 +42,13 @@ fn fire() { loop { // More complex pattern for the identity arm and diverging arm let v = match h() { - (Some(_), Some(_)) | (None, None) => continue, (Some(v), None) | (None, Some(v)) => v, + (Some(_), Some(_)) | (None, None) => continue, }; // Custom enums are supported as long as the "else" arm is a simple _ let v = match build_enum() { - _ => continue, Variant::Bar(v) | Variant::Baz(v) => v, + _ => continue, }; } @@ -71,6 +71,12 @@ fn fire() { Variant::Bar(_) | Variant::Baz(_) => (), _ => return, }; + + let data = [1_u8, 2, 3, 4, 0, 0, 0, 0]; + let data = match data.as_slice() { + [data @ .., 0, 0, 0, 0] | [data @ .., 0, 0] | [data @ .., 0] => data, + _ => return, + }; } fn not_fire() { @@ -125,4 +131,23 @@ fn not_fire() { Ok(v) | Err(Variant::Bar(v) | Variant::Baz(v)) => v, Err(Variant::Foo) => return, }; + + // Issue 10241 + // The non-divergent arm arrives in second position and + // may cover values already matched in the first arm. + let v = match h() { + (Some(_), Some(_)) | (None, None) => return, + (Some(v), _) | (None, Some(v)) => v, + }; + + let v = match build_enum() { + _ => return, + Variant::Bar(v) | Variant::Baz(v) => v, + }; + + let data = [1_u8, 2, 3, 4, 0, 0, 0, 0]; + let data = match data.as_slice() { + [] | [0, 0] => return, + [data @ .., 0, 0, 0, 0] | [data @ .., 0, 0] | [data @ ..] => data, + }; } diff --git a/src/tools/clippy/tests/ui/manual_let_else_match.stderr b/src/tools/clippy/tests/ui/manual_let_else_match.stderr index cd5e9a9ac..7abaa0b85 100644 --- a/src/tools/clippy/tests/ui/manual_let_else_match.stderr +++ b/src/tools/clippy/tests/ui/manual_let_else_match.stderr @@ -22,8 +22,8 @@ error: this could be rewritten as `let...else` --> $DIR/manual_let_else_match.rs:44:9 | LL | / let v = match h() { -LL | | (Some(_), Some(_)) | (None, None) => continue, LL | | (Some(v), None) | (None, Some(v)) => v, +LL | | (Some(_), Some(_)) | (None, None) => continue, LL | | }; | |__________^ help: consider writing: `let ((Some(v), None) | (None, Some(v))) = h() else { continue };` @@ -31,8 +31,8 @@ error: this could be rewritten as `let...else` --> $DIR/manual_let_else_match.rs:49:9 | LL | / let v = match build_enum() { -LL | | _ => continue, LL | | Variant::Bar(v) | Variant::Baz(v) => v, +LL | | _ => continue, LL | | }; | |__________^ help: consider writing: `let (Variant::Bar(v) | Variant::Baz(v)) = build_enum() else { continue };` @@ -63,5 +63,14 @@ LL | | _ => return, LL | | }; | |______^ help: consider writing: `let (Variant::Bar(_) | Variant::Baz(_)) = f else { return };` -error: aborting due to 7 previous errors +error: this could be rewritten as `let...else` + --> $DIR/manual_let_else_match.rs:76:5 + | +LL | / let data = match data.as_slice() { +LL | | [data @ .., 0, 0, 0, 0] | [data @ .., 0, 0] | [data @ .., 0] => data, +LL | | _ => return, +LL | | }; + | |______^ help: consider writing: `let ([data @ .., 0, 0, 0, 0] | [data @ .., 0, 0] | [data @ .., 0]) = data.as_slice() else { return };` + +error: aborting due to 8 previous errors diff --git a/src/tools/clippy/tests/ui/map_flatten_fixable.fixed b/src/tools/clippy/tests/ui/map_flatten_fixable.fixed index 53628ef65..8e2f11389 100644 --- a/src/tools/clippy/tests/ui/map_flatten_fixable.fixed +++ b/src/tools/clippy/tests/ui/map_flatten_fixable.fixed @@ -1,6 +1,7 @@ // run-rustfix #![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::let_underscore_untyped)] #![allow(clippy::missing_docs_in_private_items)] #![allow(clippy::map_identity)] #![allow(clippy::redundant_closure)] diff --git a/src/tools/clippy/tests/ui/map_flatten_fixable.rs b/src/tools/clippy/tests/ui/map_flatten_fixable.rs index 76016c8ed..a783a99c4 100644 --- a/src/tools/clippy/tests/ui/map_flatten_fixable.rs +++ b/src/tools/clippy/tests/ui/map_flatten_fixable.rs @@ -1,6 +1,7 @@ // run-rustfix #![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::let_underscore_untyped)] #![allow(clippy::missing_docs_in_private_items)] #![allow(clippy::map_identity)] #![allow(clippy::redundant_closure)] diff --git a/src/tools/clippy/tests/ui/map_flatten_fixable.stderr b/src/tools/clippy/tests/ui/map_flatten_fixable.stderr index b6b0c4d09..c91f0b9ae 100644 --- a/src/tools/clippy/tests/ui/map_flatten_fixable.stderr +++ b/src/tools/clippy/tests/ui/map_flatten_fixable.stderr @@ -1,5 +1,5 @@ error: called `map(..).flatten()` on `Iterator` - --> $DIR/map_flatten_fixable.rs:17:47 + --> $DIR/map_flatten_fixable.rs:18:47 | LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `filter_map` and remove the `.flatten()`: `filter_map(option_id)` @@ -7,43 +7,43 @@ LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().coll = note: `-D clippy::map-flatten` implied by `-D warnings` error: called `map(..).flatten()` on `Iterator` - --> $DIR/map_flatten_fixable.rs:18:47 + --> $DIR/map_flatten_fixable.rs:19:47 | LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `filter_map` and remove the `.flatten()`: `filter_map(option_id_ref)` error: called `map(..).flatten()` on `Iterator` - --> $DIR/map_flatten_fixable.rs:19:47 + --> $DIR/map_flatten_fixable.rs:20:47 | LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `filter_map` and remove the `.flatten()`: `filter_map(option_id_closure)` error: called `map(..).flatten()` on `Iterator` - --> $DIR/map_flatten_fixable.rs:20:47 + --> $DIR/map_flatten_fixable.rs:21:47 | LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `filter_map` and remove the `.flatten()`: `filter_map(|x| x.checked_add(1))` error: called `map(..).flatten()` on `Iterator` - --> $DIR/map_flatten_fixable.rs:23:47 + --> $DIR/map_flatten_fixable.rs:24:47 | LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect(); | ^^^^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `flat_map` and remove the `.flatten()`: `flat_map(|x| 0..x)` error: called `map(..).flatten()` on `Option` - --> $DIR/map_flatten_fixable.rs:26:40 + --> $DIR/map_flatten_fixable.rs:27:40 | LL | let _: Option<_> = (Some(Some(1))).map(|x| x).flatten(); | ^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `and_then` and remove the `.flatten()`: `and_then(|x| x)` error: called `map(..).flatten()` on `Result` - --> $DIR/map_flatten_fixable.rs:29:42 + --> $DIR/map_flatten_fixable.rs:30:42 | LL | let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten(); | ^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `and_then` and remove the `.flatten()`: `and_then(|x| x)` error: called `map(..).flatten()` on `Iterator` - --> $DIR/map_flatten_fixable.rs:38:10 + --> $DIR/map_flatten_fixable.rs:39:10 | LL | .map(|n| match n { | __________^ @@ -72,7 +72,7 @@ LL ~ }); | error: called `map(..).flatten()` on `Option` - --> $DIR/map_flatten_fixable.rs:58:10 + --> $DIR/map_flatten_fixable.rs:59:10 | LL | .map(|_| { | __________^ diff --git a/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.fixed b/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.fixed index fc252cdd3..9fd3739b6 100644 --- a/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.fixed +++ b/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.fixed @@ -123,7 +123,7 @@ fn main() { Enum::A => (), Enum::B => (), Enum::C => (), - _ => (), + Enum::__Private => (), } match Enum::A { Enum::A => (), diff --git a/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.stderr b/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.stderr index 6fa313dc9..105b4c4b4 100644 --- a/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.stderr +++ b/src/tools/clippy/tests/ui/match_wildcard_for_single_variants.stderr @@ -49,10 +49,16 @@ LL | _ => (), | ^ help: try this: `Color::Blue` error: wildcard matches only a single variant and will also match any future added variants + --> $DIR/match_wildcard_for_single_variants.rs:126:13 + | +LL | _ => (), + | ^ help: try this: `Enum::__Private` + +error: wildcard matches only a single variant and will also match any future added variants --> $DIR/match_wildcard_for_single_variants.rs:153:13 | LL | _ => 2, | ^ help: try this: `Foo::B` -error: aborting due to 9 previous errors +error: aborting due to 10 previous errors diff --git a/src/tools/clippy/tests/ui/methods.rs b/src/tools/clippy/tests/ui/methods.rs index 6f22366ea..1519e4da9 100644 --- a/src/tools/clippy/tests/ui/methods.rs +++ b/src/tools/clippy/tests/ui/methods.rs @@ -4,6 +4,7 @@ #![allow( clippy::disallowed_names, clippy::default_trait_access, + clippy::let_underscore_untyped, clippy::missing_docs_in_private_items, clippy::missing_safety_doc, clippy::non_ascii_literal, diff --git a/src/tools/clippy/tests/ui/methods.stderr b/src/tools/clippy/tests/ui/methods.stderr index b63672dd6..4643e09e2 100644 --- a/src/tools/clippy/tests/ui/methods.stderr +++ b/src/tools/clippy/tests/ui/methods.stderr @@ -1,5 +1,5 @@ error: methods called `new` usually return `Self` - --> $DIR/methods.rs:104:5 + --> $DIR/methods.rs:105:5 | LL | / fn new() -> i32 { LL | | 0 @@ -9,7 +9,7 @@ LL | | } = note: `-D clippy::new-ret-no-self` implied by `-D warnings` error: called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling `.find(..)` instead - --> $DIR/methods.rs:125:13 + --> $DIR/methods.rs:126:13 | LL | let _ = v.iter().filter(|&x| { | _____________^ diff --git a/src/tools/clippy/tests/ui/missing_doc_impl.stderr b/src/tools/clippy/tests/ui/missing_doc_impl.stderr index f22fa19db..b410f56e1 100644 --- a/src/tools/clippy/tests/ui/missing_doc_impl.stderr +++ b/src/tools/clippy/tests/ui/missing_doc_impl.stderr @@ -51,13 +51,13 @@ LL | | fn foo_with_impl(&self) {} LL | | } | |_^ -error: missing documentation for an associated function +error: missing documentation for a method --> $DIR/missing_doc_impl.rs:44:5 | LL | fn foo(&self); | ^^^^^^^^^^^^^^ -error: missing documentation for an associated function +error: missing documentation for a method --> $DIR/missing_doc_impl.rs:45:5 | LL | fn foo_with_impl(&self) {} diff --git a/src/tools/clippy/tests/ui/module_name_repetitions.stderr b/src/tools/clippy/tests/ui/module_name_repetitions.stderr index 3f343a3e4..277801194 100644 --- a/src/tools/clippy/tests/ui/module_name_repetitions.stderr +++ b/src/tools/clippy/tests/ui/module_name_repetitions.stderr @@ -1,34 +1,34 @@ error: item name starts with its containing module's name - --> $DIR/module_name_repetitions.rs:8:5 + --> $DIR/module_name_repetitions.rs:8:12 | LL | pub fn foo_bar() {} - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^ | = note: `-D clippy::module-name-repetitions` implied by `-D warnings` error: item name ends with its containing module's name - --> $DIR/module_name_repetitions.rs:9:5 + --> $DIR/module_name_repetitions.rs:9:12 | LL | pub fn bar_foo() {} - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^ error: item name starts with its containing module's name - --> $DIR/module_name_repetitions.rs:10:5 + --> $DIR/module_name_repetitions.rs:10:16 | LL | pub struct FooCake; - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^ error: item name ends with its containing module's name - --> $DIR/module_name_repetitions.rs:11:5 + --> $DIR/module_name_repetitions.rs:11:14 | LL | pub enum CakeFoo {} - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^ error: item name starts with its containing module's name - --> $DIR/module_name_repetitions.rs:12:5 + --> $DIR/module_name_repetitions.rs:12:16 | LL | pub struct Foo7Bar; - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^ error: aborting due to 5 previous errors diff --git a/src/tools/clippy/tests/ui/multiple_unsafe_ops_per_block.rs b/src/tools/clippy/tests/ui/multiple_unsafe_ops_per_block.rs new file mode 100644 index 000000000..4511bc99c --- /dev/null +++ b/src/tools/clippy/tests/ui/multiple_unsafe_ops_per_block.rs @@ -0,0 +1,119 @@ +// aux-build:macro_rules.rs +#![allow(unused)] +#![allow(deref_nullptr)] +#![allow(clippy::unnecessary_operation)] +#![allow(clippy::drop_copy)] +#![warn(clippy::multiple_unsafe_ops_per_block)] + +#[macro_use] +extern crate macro_rules; + +use core::arch::asm; + +fn raw_ptr() -> *const () { + core::ptr::null() +} + +unsafe fn not_very_safe() {} + +struct Sample; + +impl Sample { + unsafe fn not_very_safe(&self) {} +} + +#[allow(non_upper_case_globals)] +const sample: Sample = Sample; + +union U { + i: i32, + u: u32, +} + +static mut STATIC: i32 = 0; + +fn test1() { + unsafe { + STATIC += 1; + not_very_safe(); + } +} + +fn test2() { + let u = U { i: 0 }; + + unsafe { + drop(u.u); + *raw_ptr(); + } +} + +fn test3() { + unsafe { + asm!("nop"); + sample.not_very_safe(); + STATIC = 0; + } +} + +fn test_all() { + let u = U { i: 0 }; + unsafe { + drop(u.u); + drop(STATIC); + sample.not_very_safe(); + not_very_safe(); + *raw_ptr(); + asm!("nop"); + } +} + +// no lint +fn correct1() { + unsafe { + STATIC += 1; + } +} + +// no lint +fn correct2() { + unsafe { + STATIC += 1; + } + + unsafe { + *raw_ptr(); + } +} + +// no lint +fn correct3() { + let u = U { u: 0 }; + + unsafe { + not_very_safe(); + } + + unsafe { + drop(u.i); + } +} + +// tests from the issue (https://github.com/rust-lang/rust-clippy/issues/10064) + +unsafe fn read_char_bad(ptr: *const u8) -> char { + unsafe { char::from_u32_unchecked(*ptr.cast::<u32>()) } +} + +// no lint +unsafe fn read_char_good(ptr: *const u8) -> char { + let int_value = unsafe { *ptr.cast::<u32>() }; + unsafe { core::char::from_u32_unchecked(int_value) } +} + +// no lint +fn issue10259() { + unsafe_macro!(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/multiple_unsafe_ops_per_block.stderr b/src/tools/clippy/tests/ui/multiple_unsafe_ops_per_block.stderr new file mode 100644 index 000000000..303aeb7ae --- /dev/null +++ b/src/tools/clippy/tests/ui/multiple_unsafe_ops_per_block.stderr @@ -0,0 +1,129 @@ +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> $DIR/multiple_unsafe_ops_per_block.rs:36:5 + | +LL | / unsafe { +LL | | STATIC += 1; +LL | | not_very_safe(); +LL | | } + | |_____^ + | +note: modification of a mutable static occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:37:9 + | +LL | STATIC += 1; + | ^^^^^^^^^^^ +note: unsafe function call occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:38:9 + | +LL | not_very_safe(); + | ^^^^^^^^^^^^^^^ + = note: `-D clippy::multiple-unsafe-ops-per-block` implied by `-D warnings` + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> $DIR/multiple_unsafe_ops_per_block.rs:45:5 + | +LL | / unsafe { +LL | | drop(u.u); +LL | | *raw_ptr(); +LL | | } + | |_____^ + | +note: union field access occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:46:14 + | +LL | drop(u.u); + | ^^^ +note: raw pointer dereference occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:47:9 + | +LL | *raw_ptr(); + | ^^^^^^^^^^ + +error: this `unsafe` block contains 3 unsafe operations, expected only one + --> $DIR/multiple_unsafe_ops_per_block.rs:52:5 + | +LL | / unsafe { +LL | | asm!("nop"); +LL | | sample.not_very_safe(); +LL | | STATIC = 0; +LL | | } + | |_____^ + | +note: inline assembly used here + --> $DIR/multiple_unsafe_ops_per_block.rs:53:9 + | +LL | asm!("nop"); + | ^^^^^^^^^^^ +note: unsafe method call occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:54:9 + | +LL | sample.not_very_safe(); + | ^^^^^^^^^^^^^^^^^^^^^^ +note: modification of a mutable static occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:55:9 + | +LL | STATIC = 0; + | ^^^^^^^^^^ + +error: this `unsafe` block contains 6 unsafe operations, expected only one + --> $DIR/multiple_unsafe_ops_per_block.rs:61:5 + | +LL | / unsafe { +LL | | drop(u.u); +LL | | drop(STATIC); +LL | | sample.not_very_safe(); +... | +LL | | asm!("nop"); +LL | | } + | |_____^ + | +note: union field access occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:62:14 + | +LL | drop(u.u); + | ^^^ +note: access of a mutable static occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:63:14 + | +LL | drop(STATIC); + | ^^^^^^ +note: unsafe method call occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:64:9 + | +LL | sample.not_very_safe(); + | ^^^^^^^^^^^^^^^^^^^^^^ +note: unsafe function call occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:65:9 + | +LL | not_very_safe(); + | ^^^^^^^^^^^^^^^ +note: raw pointer dereference occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:66:9 + | +LL | *raw_ptr(); + | ^^^^^^^^^^ +note: inline assembly used here + --> $DIR/multiple_unsafe_ops_per_block.rs:67:9 + | +LL | asm!("nop"); + | ^^^^^^^^^^^ + +error: this `unsafe` block contains 2 unsafe operations, expected only one + --> $DIR/multiple_unsafe_ops_per_block.rs:105:5 + | +LL | unsafe { char::from_u32_unchecked(*ptr.cast::<u32>()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: unsafe function call occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:105:14 + | +LL | unsafe { char::from_u32_unchecked(*ptr.cast::<u32>()) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: raw pointer dereference occurs here + --> $DIR/multiple_unsafe_ops_per_block.rs:105:39 + | +LL | unsafe { char::from_u32_unchecked(*ptr.cast::<u32>()) } + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/must_use_candidates.fixed b/src/tools/clippy/tests/ui/must_use_candidates.fixed index 04a74a009..bbbb3cf62 100644 --- a/src/tools/clippy/tests/ui/must_use_candidates.fixed +++ b/src/tools/clippy/tests/ui/must_use_candidates.fixed @@ -84,7 +84,7 @@ pub unsafe fn mutates_static() -> usize { } #[no_mangle] -pub fn unmangled(i: bool) -> bool { +pub extern "C" fn unmangled(i: bool) -> bool { !i } diff --git a/src/tools/clippy/tests/ui/must_use_candidates.rs b/src/tools/clippy/tests/ui/must_use_candidates.rs index f04122f4e..94d3c83bd 100644 --- a/src/tools/clippy/tests/ui/must_use_candidates.rs +++ b/src/tools/clippy/tests/ui/must_use_candidates.rs @@ -84,7 +84,7 @@ pub unsafe fn mutates_static() -> usize { } #[no_mangle] -pub fn unmangled(i: bool) -> bool { +pub extern "C" fn unmangled(i: bool) -> bool { !i } diff --git a/src/tools/clippy/tests/ui/needless_lifetimes.fixed b/src/tools/clippy/tests/ui/needless_lifetimes.fixed new file mode 100644 index 000000000..f0f1f9298 --- /dev/null +++ b/src/tools/clippy/tests/ui/needless_lifetimes.fixed @@ -0,0 +1,548 @@ +// run-rustfix +// aux-build:macro_rules.rs + +#![warn(clippy::needless_lifetimes)] +#![allow( + unused, + clippy::boxed_local, + clippy::extra_unused_type_parameters, + clippy::needless_pass_by_value, + clippy::unnecessary_wraps, + dyn_drop, + clippy::get_first +)] + +#[macro_use] +extern crate macro_rules; + +fn distinct_lifetimes(_x: &u8, _y: &u8, _z: u8) {} + +fn distinct_and_static(_x: &u8, _y: &u8, _z: &'static u8) {} + +// No error; same lifetime on two params. +fn same_lifetime_on_input<'a>(_x: &'a u8, _y: &'a u8) {} + +// No error; static involved. +fn only_static_on_input(_x: &u8, _y: &u8, _z: &'static u8) {} + +fn mut_and_static_input(_x: &mut u8, _y: &'static str) {} + +fn in_and_out(x: &u8, _y: u8) -> &u8 { + x +} + +// No error; multiple input refs. +fn multiple_in_and_out_1<'a>(x: &'a u8, _y: &'a u8) -> &'a u8 { + x +} + +// Error; multiple input refs, but the output lifetime is not elided, i.e., the following is valid: +// fn multiple_in_and_out_2a<'a>(x: &'a u8, _y: &u8) -> &'a u8 +// ^^^ +fn multiple_in_and_out_2a<'a>(x: &'a u8, _y: &u8) -> &'a u8 { + x +} + +// Error; multiple input refs, but the output lifetime is not elided, i.e., the following is valid: +// fn multiple_in_and_out_2b<'b>(_x: &u8, y: &'b u8) -> &'b u8 +// ^^^ +fn multiple_in_and_out_2b<'b>(_x: &u8, y: &'b u8) -> &'b u8 { + y +} + +// No error; multiple input refs +async fn func<'a>(args: &[&'a str]) -> Option<&'a str> { + args.get(0).cloned() +} + +// No error; static involved. +fn in_static_and_out<'a>(x: &'a u8, _y: &'static u8) -> &'a u8 { + x +} + +// Error; multiple input refs, but the output lifetime is not elided, i.e., the following is valid: +// fn deep_reference_1a<'a>(x: &'a u8, _y: &u8) -> Result<&'a u8, ()> +// ^^^ +fn deep_reference_1a<'a>(x: &'a u8, _y: &u8) -> Result<&'a u8, ()> { + Ok(x) +} + +// Error; multiple input refs, but the output lifetime is not elided, i.e., the following is valid: +// fn deep_reference_1b<'b>(_x: &u8, y: &'b u8) -> Result<&'b u8, ()> +// ^^^ +fn deep_reference_1b<'b>(_x: &u8, y: &'b u8) -> Result<&'b u8, ()> { + Ok(y) +} + +// No error; two input refs. +fn deep_reference_2<'a>(x: Result<&'a u8, &'a u8>) -> &'a u8 { + x.unwrap() +} + +fn deep_reference_3(x: &u8, _y: u8) -> Result<&u8, ()> { + Ok(x) +} + +// Where-clause, but without lifetimes. +fn where_clause_without_lt<T>(x: &u8, _y: u8) -> Result<&u8, ()> +where + T: Copy, +{ + Ok(x) +} + +type Ref<'r> = &'r u8; + +// No error; same lifetime on two params. +fn lifetime_param_1<'a>(_x: Ref<'a>, _y: &'a u8) {} + +fn lifetime_param_2(_x: Ref<'_>, _y: &u8) {} + +// No error; bounded lifetime. +fn lifetime_param_3<'a, 'b: 'a>(_x: Ref<'a>, _y: &'b u8) {} + +// No error; bounded lifetime. +fn lifetime_param_4<'a, 'b>(_x: Ref<'a>, _y: &'b u8) +where + 'b: 'a, +{ +} + +struct Lt<'a, I: 'static> { + x: &'a I, +} + +// No error; fn bound references `'a`. +fn fn_bound<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I> +where + F: Fn(Lt<'a, I>) -> Lt<'a, I>, +{ + unreachable!() +} + +fn fn_bound_2<F, I>(_m: Lt<'_, I>, _f: F) -> Lt<'_, I> +where + for<'x> F: Fn(Lt<'x, I>) -> Lt<'x, I>, +{ + unreachable!() +} + +// No error; see below. +fn fn_bound_3<'a, F: FnOnce(&'a i32)>(x: &'a i32, f: F) { + f(x); +} + +fn fn_bound_3_cannot_elide() { + let x = 42; + let p = &x; + let mut q = &x; + // This will fail if we elide lifetimes of `fn_bound_3`. + fn_bound_3(p, |y| q = y); +} + +// No error; multiple input refs. +fn fn_bound_4<'a, F: FnOnce() -> &'a ()>(cond: bool, x: &'a (), f: F) -> &'a () { + if cond { x } else { f() } +} + +struct X { + x: u8, +} + +impl X { + fn self_and_out(&self) -> &u8 { + &self.x + } + + // Error; multiple input refs, but the output lifetime is not elided, i.e., the following is valid: + // fn self_and_in_out_1<'s>(&'s self, _x: &u8) -> &'s u8 + // ^^^ + fn self_and_in_out_1<'s>(&'s self, _x: &u8) -> &'s u8 { + &self.x + } + + // Error; multiple input refs, but the output lifetime is not elided, i.e., the following is valid: + // fn self_and_in_out_2<'t>(&self, x: &'t u8) -> &'t u8 + // ^^^^^ + fn self_and_in_out_2<'t>(&self, x: &'t u8) -> &'t u8 { + x + } + + fn distinct_self_and_in(&self, _x: &u8) {} + + // No error; same lifetimes on two params. + fn self_and_same_in<'s>(&'s self, _x: &'s u8) {} +} + +struct Foo<'a>(&'a u8); + +impl<'a> Foo<'a> { + // No error; lifetime `'a` not defined in method. + fn self_shared_lifetime(&self, _: &'a u8) {} + // No error; bounds exist. + fn self_bound_lifetime<'b: 'a>(&self, _: &'b u8) {} +} + +fn already_elided<'a>(_: &u8, _: &'a u8) -> &'a u8 { + unimplemented!() +} + +fn struct_with_lt(_foo: Foo<'_>) -> &str { + unimplemented!() +} + +// No warning; two input lifetimes (named on the reference, anonymous on `Foo`). +fn struct_with_lt2<'a>(_foo: &'a Foo) -> &'a str { + unimplemented!() +} + +// No warning; two input lifetimes (anonymous on the reference, named on `Foo`). +fn struct_with_lt3<'a>(_foo: &Foo<'a>) -> &'a str { + unimplemented!() +} + +// Warning; two input lifetimes, but the output lifetime is not elided, i.e., the following is +// valid: +// fn struct_with_lt4a<'a>(_foo: &'a Foo<'_>) -> &'a str +// ^^ +fn struct_with_lt4a<'a>(_foo: &'a Foo<'_>) -> &'a str { + unimplemented!() +} + +// Warning; two input lifetimes, but the output lifetime is not elided, i.e., the following is +// valid: +// fn struct_with_lt4b<'b>(_foo: &Foo<'b>) -> &'b str +// ^^^^ +fn struct_with_lt4b<'b>(_foo: &Foo<'b>) -> &'b str { + unimplemented!() +} + +trait WithLifetime<'a> {} + +type WithLifetimeAlias<'a> = dyn WithLifetime<'a>; + +// Should not warn because it won't build without the lifetime. +fn trait_obj_elided<'a>(_arg: &'a dyn WithLifetime) -> &'a str { + unimplemented!() +} + +// Should warn because there is no lifetime on `Drop`, so this would be +// unambiguous if we elided the lifetime. +fn trait_obj_elided2(_arg: &dyn Drop) -> &str { + unimplemented!() +} + +type FooAlias<'a> = Foo<'a>; + +fn alias_with_lt(_foo: FooAlias<'_>) -> &str { + unimplemented!() +} + +// No warning; two input lifetimes (named on the reference, anonymous on `FooAlias`). +fn alias_with_lt2<'a>(_foo: &'a FooAlias) -> &'a str { + unimplemented!() +} + +// No warning; two input lifetimes (anonymous on the reference, named on `FooAlias`). +fn alias_with_lt3<'a>(_foo: &FooAlias<'a>) -> &'a str { + unimplemented!() +} + +// Warning; two input lifetimes, but the output lifetime is not elided, i.e., the following is +// valid: +// fn alias_with_lt4a<'a>(_foo: &'a FooAlias<'_>) -> &'a str +// ^^ +fn alias_with_lt4a<'a>(_foo: &'a FooAlias<'_>) -> &'a str { + unimplemented!() +} + +// Warning; two input lifetimes, but the output lifetime is not elided, i.e., the following is +// valid: +// fn alias_with_lt4b<'b>(_foo: &FooAlias<'b>) -> &'b str +// ^^^^^^^^^ +fn alias_with_lt4b<'b>(_foo: &FooAlias<'b>) -> &'b str { + unimplemented!() +} + +fn named_input_elided_output(_arg: &str) -> &str { + unimplemented!() +} + +fn elided_input_named_output<'a>(_arg: &str) -> &'a str { + unimplemented!() +} + +fn trait_bound_ok<T: WithLifetime<'static>>(_: &u8, _: T) { + unimplemented!() +} +fn trait_bound<'a, T: WithLifetime<'a>>(_: &'a u8, _: T) { + unimplemented!() +} + +// Don't warn on these; see issue #292. +fn trait_bound_bug<'a, T: WithLifetime<'a>>() { + unimplemented!() +} + +// See issue #740. +struct Test { + vec: Vec<usize>, +} + +impl Test { + fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = usize> + 'a> { + unimplemented!() + } +} + +trait LintContext<'a> {} + +fn f<'a, T: LintContext<'a>>(_: &T) {} + +fn test<'a>(x: &'a [u8]) -> u8 { + let y: &'a u8 = &x[5]; + *y +} + +// Issue #3284: give hint regarding lifetime in return type. +struct Cow<'a> { + x: &'a str, +} +fn out_return_type_lts(e: &str) -> Cow<'_> { + unimplemented!() +} + +// Make sure we still warn on implementations +mod issue4291 { + trait BadTrait { + fn needless_lt(x: &u8) {} + } + + impl BadTrait for () { + fn needless_lt(_x: &u8) {} + } +} + +mod issue2944 { + trait Foo {} + struct Bar; + struct Baz<'a> { + bar: &'a Bar, + } + + impl<'a> Foo for Baz<'a> {} + impl Bar { + fn baz(&self) -> impl Foo + '_ { + Baz { bar: self } + } + } +} + +mod nested_elision_sites { + // issue #issue2944 + + // closure trait bounds subject to nested elision + // don't lint because they refer to outer lifetimes + fn trait_fn<'a>(i: &'a i32) -> impl Fn() -> &'a i32 { + move || i + } + fn trait_fn_mut<'a>(i: &'a i32) -> impl FnMut() -> &'a i32 { + move || i + } + fn trait_fn_once<'a>(i: &'a i32) -> impl FnOnce() -> &'a i32 { + move || i + } + + // don't lint + fn impl_trait_in_input_position<'a>(f: impl Fn() -> &'a i32) -> &'a i32 { + f() + } + fn impl_trait_in_output_position<'a>(i: &'a i32) -> impl Fn() -> &'a i32 { + move || i + } + // lint + fn impl_trait_elidable_nested_named_lifetimes<'a>(i: &'a i32, f: impl for<'b> Fn(&'b i32) -> &'b i32) -> &'a i32 { + f(i) + } + fn impl_trait_elidable_nested_anonymous_lifetimes(i: &i32, f: impl Fn(&i32) -> &i32) -> &i32 { + f(i) + } + + // don't lint + fn generics_not_elidable<'a, T: Fn() -> &'a i32>(f: T) -> &'a i32 { + f() + } + // lint + fn generics_elidable<T: Fn(&i32) -> &i32>(i: &i32, f: T) -> &i32 { + f(i) + } + + // don't lint + fn where_clause_not_elidable<'a, T>(f: T) -> &'a i32 + where + T: Fn() -> &'a i32, + { + f() + } + // lint + fn where_clause_elidadable<T>(i: &i32, f: T) -> &i32 + where + T: Fn(&i32) -> &i32, + { + f(i) + } + + // don't lint + fn pointer_fn_in_input_position<'a>(f: fn(&'a i32) -> &'a i32, i: &'a i32) -> &'a i32 { + f(i) + } + fn pointer_fn_in_output_position<'a>(_: &'a i32) -> fn(&'a i32) -> &'a i32 { + |i| i + } + // lint + fn pointer_fn_elidable(i: &i32, f: fn(&i32) -> &i32) -> &i32 { + f(i) + } + + // don't lint + fn nested_fn_pointer_1<'a>(_: &'a i32) -> fn(fn(&'a i32) -> &'a i32) -> i32 { + |f| 42 + } + fn nested_fn_pointer_2<'a>(_: &'a i32) -> impl Fn(fn(&'a i32)) { + |f| () + } + + // lint + fn nested_fn_pointer_3(_: &i32) -> fn(fn(&i32) -> &i32) -> i32 { + |f| 42 + } + fn nested_fn_pointer_4(_: &i32) -> impl Fn(fn(&i32)) { + |f| () + } +} + +mod issue6159 { + use std::ops::Deref; + pub fn apply_deref<'a, T, F, R>(x: &'a T, f: F) -> R + where + T: Deref, + F: FnOnce(&'a T::Target) -> R, + { + f(x.deref()) + } +} + +mod issue7296 { + use std::rc::Rc; + use std::sync::Arc; + + struct Foo; + impl Foo { + fn implicit(&self) -> &() { + &() + } + fn implicit_mut(&mut self) -> &() { + &() + } + + fn explicit<'a>(self: &'a Arc<Self>) -> &'a () { + &() + } + fn explicit_mut<'a>(self: &'a mut Rc<Self>) -> &'a () { + &() + } + + fn lifetime_elsewhere(self: Box<Self>, here: &()) -> &() { + &() + } + } + + trait Bar { + fn implicit(&self) -> &(); + fn implicit_provided(&self) -> &() { + &() + } + + fn explicit<'a>(self: &'a Arc<Self>) -> &'a (); + fn explicit_provided<'a>(self: &'a Arc<Self>) -> &'a () { + &() + } + + fn lifetime_elsewhere(self: Box<Self>, here: &()) -> &(); + fn lifetime_elsewhere_provided(self: Box<Self>, here: &()) -> &() { + &() + } + } +} + +mod pr_9743_false_negative_fix { + #![allow(unused)] + + fn foo(x: &u8, y: &'_ u8) {} + + fn bar(x: &u8, y: &'_ u8, z: &'_ u8) {} +} + +mod pr_9743_output_lifetime_checks { + #![allow(unused)] + + // lint: only one input + fn one_input(x: &u8) -> &u8 { + unimplemented!() + } + + // lint: multiple inputs, output would not be elided + fn multiple_inputs_output_not_elided<'b>(x: &u8, y: &'b u8, z: &'b u8) -> &'b u8 { + unimplemented!() + } + + // don't lint: multiple inputs, output would be elided (which would create an ambiguity) + fn multiple_inputs_output_would_be_elided<'a, 'b>(x: &'a u8, y: &'b u8, z: &'b u8) -> &'a u8 { + unimplemented!() + } +} + +mod in_macro { + macro_rules! local_one_input_macro { + () => { + fn one_input(x: &u8) -> &u8 { + unimplemented!() + } + }; + } + + // lint local macro expands to function with needless lifetimes + local_one_input_macro!(); + + // no lint on external macro + macro_rules::needless_lifetime!(); + + macro_rules! expanded_lifetime { + ($l:lifetime) => { + fn f<$l>(arg: &$l str) -> &$l str { + arg + } + } + } + + expanded_lifetime!('a); +} + +mod issue5787 { + use std::sync::MutexGuard; + + struct Foo; + + impl Foo { + // doesn't get linted without async + pub async fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> { + guard + } + } + + async fn foo<'a>(_x: &i32, y: &'a str) -> &'a str { + y + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/needless_lifetimes.rs b/src/tools/clippy/tests/ui/needless_lifetimes.rs index 2efc93675..ddfd10430 100644 --- a/src/tools/clippy/tests/ui/needless_lifetimes.rs +++ b/src/tools/clippy/tests/ui/needless_lifetimes.rs @@ -1,13 +1,20 @@ +// run-rustfix +// aux-build:macro_rules.rs + #![warn(clippy::needless_lifetimes)] #![allow( - dead_code, + unused, clippy::boxed_local, + clippy::extra_unused_type_parameters, clippy::needless_pass_by_value, clippy::unnecessary_wraps, dyn_drop, clippy::get_first )] +#[macro_use] +extern crate macro_rules; + fn distinct_lifetimes<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: u8) {} fn distinct_and_static<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: &'static u8) {} @@ -495,4 +502,47 @@ mod pr_9743_output_lifetime_checks { } } +mod in_macro { + macro_rules! local_one_input_macro { + () => { + fn one_input<'a>(x: &'a u8) -> &'a u8 { + unimplemented!() + } + }; + } + + // lint local macro expands to function with needless lifetimes + local_one_input_macro!(); + + // no lint on external macro + macro_rules::needless_lifetime!(); + + macro_rules! expanded_lifetime { + ($l:lifetime) => { + fn f<$l>(arg: &$l str) -> &$l str { + arg + } + } + } + + expanded_lifetime!('a); +} + +mod issue5787 { + use std::sync::MutexGuard; + + struct Foo; + + impl Foo { + // doesn't get linted without async + pub async fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> { + guard + } + } + + async fn foo<'a>(_x: &i32, y: &'a str) -> &'a str { + y + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/needless_lifetimes.stderr b/src/tools/clippy/tests/ui/needless_lifetimes.stderr index 5a7cf13c8..4e3c8f20d 100644 --- a/src/tools/clippy/tests/ui/needless_lifetimes.stderr +++ b/src/tools/clippy/tests/ui/needless_lifetimes.stderr @@ -1,316 +1,559 @@ error: the following explicit lifetimes could be elided: 'a, 'b - --> $DIR/needless_lifetimes.rs:11:1 + --> $DIR/needless_lifetimes.rs:18:1 | LL | fn distinct_lifetimes<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: u8) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::needless-lifetimes` implied by `-D warnings` +help: elide the lifetimes + | +LL - fn distinct_lifetimes<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: u8) {} +LL + fn distinct_lifetimes(_x: &u8, _y: &u8, _z: u8) {} + | error: the following explicit lifetimes could be elided: 'a, 'b - --> $DIR/needless_lifetimes.rs:13:1 + --> $DIR/needless_lifetimes.rs:20:1 | LL | fn distinct_and_static<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: &'static u8) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn distinct_and_static<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: &'static u8) {} +LL + fn distinct_and_static(_x: &u8, _y: &u8, _z: &'static u8) {} + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:23:1 + --> $DIR/needless_lifetimes.rs:30:1 | LL | fn in_and_out<'a>(x: &'a u8, _y: u8) -> &'a u8 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn in_and_out<'a>(x: &'a u8, _y: u8) -> &'a u8 { +LL + fn in_and_out(x: &u8, _y: u8) -> &u8 { + | error: the following explicit lifetimes could be elided: 'b - --> $DIR/needless_lifetimes.rs:35:1 + --> $DIR/needless_lifetimes.rs:42:1 | LL | fn multiple_in_and_out_2a<'a, 'b>(x: &'a u8, _y: &'b u8) -> &'a u8 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn multiple_in_and_out_2a<'a, 'b>(x: &'a u8, _y: &'b u8) -> &'a u8 { +LL + fn multiple_in_and_out_2a<'a>(x: &'a u8, _y: &u8) -> &'a u8 { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:42:1 + --> $DIR/needless_lifetimes.rs:49:1 | LL | fn multiple_in_and_out_2b<'a, 'b>(_x: &'a u8, y: &'b u8) -> &'b u8 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn multiple_in_and_out_2b<'a, 'b>(_x: &'a u8, y: &'b u8) -> &'b u8 { +LL + fn multiple_in_and_out_2b<'b>(_x: &u8, y: &'b u8) -> &'b u8 { + | error: the following explicit lifetimes could be elided: 'b - --> $DIR/needless_lifetimes.rs:59:1 + --> $DIR/needless_lifetimes.rs:66:1 | LL | fn deep_reference_1a<'a, 'b>(x: &'a u8, _y: &'b u8) -> Result<&'a u8, ()> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn deep_reference_1a<'a, 'b>(x: &'a u8, _y: &'b u8) -> Result<&'a u8, ()> { +LL + fn deep_reference_1a<'a>(x: &'a u8, _y: &u8) -> Result<&'a u8, ()> { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:66:1 + --> $DIR/needless_lifetimes.rs:73:1 | LL | fn deep_reference_1b<'a, 'b>(_x: &'a u8, y: &'b u8) -> Result<&'b u8, ()> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn deep_reference_1b<'a, 'b>(_x: &'a u8, y: &'b u8) -> Result<&'b u8, ()> { +LL + fn deep_reference_1b<'b>(_x: &u8, y: &'b u8) -> Result<&'b u8, ()> { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:75:1 + --> $DIR/needless_lifetimes.rs:82:1 | LL | fn deep_reference_3<'a>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn deep_reference_3<'a>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> { +LL + fn deep_reference_3(x: &u8, _y: u8) -> Result<&u8, ()> { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:80:1 + --> $DIR/needless_lifetimes.rs:87:1 | LL | fn where_clause_without_lt<'a, T>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn where_clause_without_lt<'a, T>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> +LL + fn where_clause_without_lt<T>(x: &u8, _y: u8) -> Result<&u8, ()> + | error: the following explicit lifetimes could be elided: 'a, 'b - --> $DIR/needless_lifetimes.rs:92:1 + --> $DIR/needless_lifetimes.rs:99:1 | LL | fn lifetime_param_2<'a, 'b>(_x: Ref<'a>, _y: &'b u8) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: replace with `'_` in generic arguments such as here - --> $DIR/needless_lifetimes.rs:92:37 +help: elide the lifetimes + | +LL - fn lifetime_param_2<'a, 'b>(_x: Ref<'a>, _y: &'b u8) {} +LL + fn lifetime_param_2(_x: Ref<'_>, _y: &u8) {} | -LL | fn lifetime_param_2<'a, 'b>(_x: Ref<'a>, _y: &'b u8) {} - | ^^ error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:116:1 + --> $DIR/needless_lifetimes.rs:123:1 | LL | fn fn_bound_2<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I> | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: replace with `'_` in generic arguments such as here - --> $DIR/needless_lifetimes.rs:116:32 +help: elide the lifetimes + | +LL - fn fn_bound_2<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I> +LL + fn fn_bound_2<F, I>(_m: Lt<'_, I>, _f: F) -> Lt<'_, I> | -LL | fn fn_bound_2<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I> - | ^^ error: the following explicit lifetimes could be elided: 's - --> $DIR/needless_lifetimes.rs:146:5 + --> $DIR/needless_lifetimes.rs:153:5 | LL | fn self_and_out<'s>(&'s self) -> &'s u8 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn self_and_out<'s>(&'s self) -> &'s u8 { +LL + fn self_and_out(&self) -> &u8 { + | error: the following explicit lifetimes could be elided: 't - --> $DIR/needless_lifetimes.rs:153:5 + --> $DIR/needless_lifetimes.rs:160:5 | LL | fn self_and_in_out_1<'s, 't>(&'s self, _x: &'t u8) -> &'s u8 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn self_and_in_out_1<'s, 't>(&'s self, _x: &'t u8) -> &'s u8 { +LL + fn self_and_in_out_1<'s>(&'s self, _x: &u8) -> &'s u8 { + | error: the following explicit lifetimes could be elided: 's - --> $DIR/needless_lifetimes.rs:160:5 + --> $DIR/needless_lifetimes.rs:167:5 | LL | fn self_and_in_out_2<'s, 't>(&'s self, x: &'t u8) -> &'t u8 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn self_and_in_out_2<'s, 't>(&'s self, x: &'t u8) -> &'t u8 { +LL + fn self_and_in_out_2<'t>(&self, x: &'t u8) -> &'t u8 { + | error: the following explicit lifetimes could be elided: 's, 't - --> $DIR/needless_lifetimes.rs:164:5 + --> $DIR/needless_lifetimes.rs:171:5 | LL | fn distinct_self_and_in<'s, 't>(&'s self, _x: &'t u8) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn distinct_self_and_in<'s, 't>(&'s self, _x: &'t u8) {} +LL + fn distinct_self_and_in(&self, _x: &u8) {} + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:183:1 + --> $DIR/needless_lifetimes.rs:190:1 | LL | fn struct_with_lt<'a>(_foo: Foo<'a>) -> &'a str { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: replace with `'_` in generic arguments such as here - --> $DIR/needless_lifetimes.rs:183:33 +help: elide the lifetimes + | +LL - fn struct_with_lt<'a>(_foo: Foo<'a>) -> &'a str { +LL + fn struct_with_lt(_foo: Foo<'_>) -> &str { | -LL | fn struct_with_lt<'a>(_foo: Foo<'a>) -> &'a str { - | ^^ error: the following explicit lifetimes could be elided: 'b - --> $DIR/needless_lifetimes.rs:201:1 + --> $DIR/needless_lifetimes.rs:208:1 | LL | fn struct_with_lt4a<'a, 'b>(_foo: &'a Foo<'b>) -> &'a str { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: replace with `'_` in generic arguments such as here - --> $DIR/needless_lifetimes.rs:201:43 +help: elide the lifetimes + | +LL - fn struct_with_lt4a<'a, 'b>(_foo: &'a Foo<'b>) -> &'a str { +LL + fn struct_with_lt4a<'a>(_foo: &'a Foo<'_>) -> &'a str { | -LL | fn struct_with_lt4a<'a, 'b>(_foo: &'a Foo<'b>) -> &'a str { - | ^^ error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:209:1 + --> $DIR/needless_lifetimes.rs:216:1 | LL | fn struct_with_lt4b<'a, 'b>(_foo: &'a Foo<'b>) -> &'b str { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn struct_with_lt4b<'a, 'b>(_foo: &'a Foo<'b>) -> &'b str { +LL + fn struct_with_lt4b<'b>(_foo: &Foo<'b>) -> &'b str { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:224:1 + --> $DIR/needless_lifetimes.rs:231:1 | LL | fn trait_obj_elided2<'a>(_arg: &'a dyn Drop) -> &'a str { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn trait_obj_elided2<'a>(_arg: &'a dyn Drop) -> &'a str { +LL + fn trait_obj_elided2(_arg: &dyn Drop) -> &str { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:230:1 + --> $DIR/needless_lifetimes.rs:237:1 | LL | fn alias_with_lt<'a>(_foo: FooAlias<'a>) -> &'a str { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: replace with `'_` in generic arguments such as here - --> $DIR/needless_lifetimes.rs:230:37 +help: elide the lifetimes + | +LL - fn alias_with_lt<'a>(_foo: FooAlias<'a>) -> &'a str { +LL + fn alias_with_lt(_foo: FooAlias<'_>) -> &str { | -LL | fn alias_with_lt<'a>(_foo: FooAlias<'a>) -> &'a str { - | ^^ error: the following explicit lifetimes could be elided: 'b - --> $DIR/needless_lifetimes.rs:248:1 + --> $DIR/needless_lifetimes.rs:255:1 | LL | fn alias_with_lt4a<'a, 'b>(_foo: &'a FooAlias<'b>) -> &'a str { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: replace with `'_` in generic arguments such as here - --> $DIR/needless_lifetimes.rs:248:47 +help: elide the lifetimes + | +LL - fn alias_with_lt4a<'a, 'b>(_foo: &'a FooAlias<'b>) -> &'a str { +LL + fn alias_with_lt4a<'a>(_foo: &'a FooAlias<'_>) -> &'a str { | -LL | fn alias_with_lt4a<'a, 'b>(_foo: &'a FooAlias<'b>) -> &'a str { - | ^^ error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:256:1 + --> $DIR/needless_lifetimes.rs:263:1 | LL | fn alias_with_lt4b<'a, 'b>(_foo: &'a FooAlias<'b>) -> &'b str { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn alias_with_lt4b<'a, 'b>(_foo: &'a FooAlias<'b>) -> &'b str { +LL + fn alias_with_lt4b<'b>(_foo: &FooAlias<'b>) -> &'b str { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:260:1 + --> $DIR/needless_lifetimes.rs:267:1 | LL | fn named_input_elided_output<'a>(_arg: &'a str) -> &str { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn named_input_elided_output<'a>(_arg: &'a str) -> &str { +LL + fn named_input_elided_output(_arg: &str) -> &str { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:268:1 + --> $DIR/needless_lifetimes.rs:275:1 | LL | fn trait_bound_ok<'a, T: WithLifetime<'static>>(_: &'a u8, _: T) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn trait_bound_ok<'a, T: WithLifetime<'static>>(_: &'a u8, _: T) { +LL + fn trait_bound_ok<T: WithLifetime<'static>>(_: &u8, _: T) { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:304:1 + --> $DIR/needless_lifetimes.rs:311:1 | LL | fn out_return_type_lts<'a>(e: &'a str) -> Cow<'a> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: replace with `'_` in generic arguments such as here - --> $DIR/needless_lifetimes.rs:304:47 +help: elide the lifetimes + | +LL - fn out_return_type_lts<'a>(e: &'a str) -> Cow<'a> { +LL + fn out_return_type_lts(e: &str) -> Cow<'_> { | -LL | fn out_return_type_lts<'a>(e: &'a str) -> Cow<'a> { - | ^^ error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:311:9 + --> $DIR/needless_lifetimes.rs:318:9 | LL | fn needless_lt<'a>(x: &'a u8) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn needless_lt<'a>(x: &'a u8) {} +LL + fn needless_lt(x: &u8) {} + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:315:9 + --> $DIR/needless_lifetimes.rs:322:9 | LL | fn needless_lt<'a>(_x: &'a u8) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn needless_lt<'a>(_x: &'a u8) {} +LL + fn needless_lt(_x: &u8) {} + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:328:9 + --> $DIR/needless_lifetimes.rs:335:9 | LL | fn baz<'a>(&'a self) -> impl Foo + 'a { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn baz<'a>(&'a self) -> impl Foo + 'a { +LL + fn baz(&self) -> impl Foo + '_ { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:360:5 + --> $DIR/needless_lifetimes.rs:367:5 | LL | fn impl_trait_elidable_nested_anonymous_lifetimes<'a>(i: &'a i32, f: impl Fn(&i32) -> &i32) -> &'a i32 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn impl_trait_elidable_nested_anonymous_lifetimes<'a>(i: &'a i32, f: impl Fn(&i32) -> &i32) -> &'a i32 { +LL + fn impl_trait_elidable_nested_anonymous_lifetimes(i: &i32, f: impl Fn(&i32) -> &i32) -> &i32 { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:369:5 + --> $DIR/needless_lifetimes.rs:376:5 | LL | fn generics_elidable<'a, T: Fn(&i32) -> &i32>(i: &'a i32, f: T) -> &'a i32 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn generics_elidable<'a, T: Fn(&i32) -> &i32>(i: &'a i32, f: T) -> &'a i32 { +LL + fn generics_elidable<T: Fn(&i32) -> &i32>(i: &i32, f: T) -> &i32 { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:381:5 + --> $DIR/needless_lifetimes.rs:388:5 | LL | fn where_clause_elidadable<'a, T>(i: &'a i32, f: T) -> &'a i32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn where_clause_elidadable<'a, T>(i: &'a i32, f: T) -> &'a i32 +LL + fn where_clause_elidadable<T>(i: &i32, f: T) -> &i32 + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:396:5 + --> $DIR/needless_lifetimes.rs:403:5 | LL | fn pointer_fn_elidable<'a>(i: &'a i32, f: fn(&i32) -> &i32) -> &'a i32 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn pointer_fn_elidable<'a>(i: &'a i32, f: fn(&i32) -> &i32) -> &'a i32 { +LL + fn pointer_fn_elidable(i: &i32, f: fn(&i32) -> &i32) -> &i32 { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:409:5 + --> $DIR/needless_lifetimes.rs:416:5 | LL | fn nested_fn_pointer_3<'a>(_: &'a i32) -> fn(fn(&i32) -> &i32) -> i32 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn nested_fn_pointer_3<'a>(_: &'a i32) -> fn(fn(&i32) -> &i32) -> i32 { +LL + fn nested_fn_pointer_3(_: &i32) -> fn(fn(&i32) -> &i32) -> i32 { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:412:5 + --> $DIR/needless_lifetimes.rs:419:5 | LL | fn nested_fn_pointer_4<'a>(_: &'a i32) -> impl Fn(fn(&i32)) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn nested_fn_pointer_4<'a>(_: &'a i32) -> impl Fn(fn(&i32)) { +LL + fn nested_fn_pointer_4(_: &i32) -> impl Fn(fn(&i32)) { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:434:9 + --> $DIR/needless_lifetimes.rs:441:9 | LL | fn implicit<'a>(&'a self) -> &'a () { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn implicit<'a>(&'a self) -> &'a () { +LL + fn implicit(&self) -> &() { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:437:9 + --> $DIR/needless_lifetimes.rs:444:9 | LL | fn implicit_mut<'a>(&'a mut self) -> &'a () { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn implicit_mut<'a>(&'a mut self) -> &'a () { +LL + fn implicit_mut(&mut self) -> &() { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:448:9 + --> $DIR/needless_lifetimes.rs:455:9 | LL | fn lifetime_elsewhere<'a>(self: Box<Self>, here: &'a ()) -> &'a () { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn lifetime_elsewhere<'a>(self: Box<Self>, here: &'a ()) -> &'a () { +LL + fn lifetime_elsewhere(self: Box<Self>, here: &()) -> &() { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:454:9 + --> $DIR/needless_lifetimes.rs:461:9 | LL | fn implicit<'a>(&'a self) -> &'a (); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn implicit<'a>(&'a self) -> &'a (); +LL + fn implicit(&self) -> &(); + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:455:9 + --> $DIR/needless_lifetimes.rs:462:9 | LL | fn implicit_provided<'a>(&'a self) -> &'a () { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn implicit_provided<'a>(&'a self) -> &'a () { +LL + fn implicit_provided(&self) -> &() { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:464:9 + --> $DIR/needless_lifetimes.rs:471:9 | LL | fn lifetime_elsewhere<'a>(self: Box<Self>, here: &'a ()) -> &'a (); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn lifetime_elsewhere<'a>(self: Box<Self>, here: &'a ()) -> &'a (); +LL + fn lifetime_elsewhere(self: Box<Self>, here: &()) -> &(); + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:465:9 + --> $DIR/needless_lifetimes.rs:472:9 | LL | fn lifetime_elsewhere_provided<'a>(self: Box<Self>, here: &'a ()) -> &'a () { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn lifetime_elsewhere_provided<'a>(self: Box<Self>, here: &'a ()) -> &'a () { +LL + fn lifetime_elsewhere_provided(self: Box<Self>, here: &()) -> &() { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:474:5 + --> $DIR/needless_lifetimes.rs:481:5 | LL | fn foo<'a>(x: &'a u8, y: &'_ u8) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn foo<'a>(x: &'a u8, y: &'_ u8) {} +LL + fn foo(x: &u8, y: &'_ u8) {} + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:476:5 + --> $DIR/needless_lifetimes.rs:483:5 | LL | fn bar<'a>(x: &'a u8, y: &'_ u8, z: &'_ u8) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn bar<'a>(x: &'a u8, y: &'_ u8, z: &'_ u8) {} +LL + fn bar(x: &u8, y: &'_ u8, z: &'_ u8) {} + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:483:5 + --> $DIR/needless_lifetimes.rs:490:5 | LL | fn one_input<'a>(x: &'a u8) -> &'a u8 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn one_input<'a>(x: &'a u8) -> &'a u8 { +LL + fn one_input(x: &u8) -> &u8 { + | error: the following explicit lifetimes could be elided: 'a - --> $DIR/needless_lifetimes.rs:488:5 + --> $DIR/needless_lifetimes.rs:495:5 | LL | fn multiple_inputs_output_not_elided<'a, 'b>(x: &'a u8, y: &'b u8, z: &'b u8) -> &'b u8 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: elide the lifetimes + | +LL - fn multiple_inputs_output_not_elided<'a, 'b>(x: &'a u8, y: &'b u8, z: &'b u8) -> &'b u8 { +LL + fn multiple_inputs_output_not_elided<'b>(x: &u8, y: &'b u8, z: &'b u8) -> &'b u8 { + | + +error: the following explicit lifetimes could be elided: 'a + --> $DIR/needless_lifetimes.rs:508:13 + | +LL | fn one_input<'a>(x: &'a u8) -> &'a u8 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | local_one_input_macro!(); + | ------------------------ in this macro invocation + | + = note: this error originates in the macro `local_one_input_macro` (in Nightly builds, run with -Z macro-backtrace for more info) +help: elide the lifetimes + | +LL - fn one_input<'a>(x: &'a u8) -> &'a u8 { +LL + fn one_input(x: &u8) -> &u8 { + | -error: aborting due to 45 previous errors +error: aborting due to 46 previous errors diff --git a/src/tools/clippy/tests/ui/needless_range_loop.stderr b/src/tools/clippy/tests/ui/needless_range_loop.stderr index b31544ec3..cffa19bec 100644 --- a/src/tools/clippy/tests/ui/needless_range_loop.stderr +++ b/src/tools/clippy/tests/ui/needless_range_loop.stderr @@ -49,7 +49,7 @@ error: the loop variable `i` is used to index `vec` LL | for i in 0..vec.len() { | ^^^^^^^^^^^^ | -help: consider using an iterator +help: consider using an iterator and enumerate() | LL | for (i, <item>) in vec.iter().enumerate() { | ~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~ @@ -126,7 +126,7 @@ error: the loop variable `i` is used to index `vec` LL | for i in 5..vec.len() { | ^^^^^^^^^^^^ | -help: consider using an iterator +help: consider using an iterator and enumerate() | LL | for (i, <item>) in vec.iter().enumerate().skip(5) { | ~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -137,7 +137,7 @@ error: the loop variable `i` is used to index `vec` LL | for i in 5..10 { | ^^^^^ | -help: consider using an iterator +help: consider using an iterator and enumerate() | LL | for (i, <item>) in vec.iter().enumerate().take(10).skip(5) { | ~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -148,7 +148,7 @@ error: the loop variable `i` is used to index `vec` LL | for i in 0..vec.len() { | ^^^^^^^^^^^^ | -help: consider using an iterator +help: consider using an iterator and enumerate() | LL | for (i, <item>) in vec.iter_mut().enumerate() { | ~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/tools/clippy/tests/ui/needless_return.fixed b/src/tools/clippy/tests/ui/needless_return.fixed index ab1c0e590..0f525dd29 100644 --- a/src/tools/clippy/tests/ui/needless_return.fixed +++ b/src/tools/clippy/tests/ui/needless_return.fixed @@ -31,6 +31,16 @@ fn test_no_semicolon() -> bool { true } +#[rustfmt::skip] +fn test_multiple_semicolon() -> bool { + true +} + +#[rustfmt::skip] +fn test_multiple_semicolon_with_spaces() -> bool { + true +} + fn test_if_block() -> bool { if true { true @@ -287,4 +297,14 @@ fn issue10051() -> Result<String, String> { } } +mod issue10049 { + fn single() -> u32 { + if true { 1 } else { 2 } + } + + fn multiple(b1: bool, b2: bool, b3: bool) -> u32 { + (if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 }) + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/needless_return.rs b/src/tools/clippy/tests/ui/needless_return.rs index abed338bb..a1db8375d 100644 --- a/src/tools/clippy/tests/ui/needless_return.rs +++ b/src/tools/clippy/tests/ui/needless_return.rs @@ -31,6 +31,16 @@ fn test_no_semicolon() -> bool { return true; } +#[rustfmt::skip] +fn test_multiple_semicolon() -> bool { + return true;;; +} + +#[rustfmt::skip] +fn test_multiple_semicolon_with_spaces() -> bool { + return true;; ; ; +} + fn test_if_block() -> bool { if true { return true; @@ -297,4 +307,14 @@ fn issue10051() -> Result<String, String> { } } +mod issue10049 { + fn single() -> u32 { + return if true { 1 } else { 2 }; + } + + fn multiple(b1: bool, b2: bool, b3: bool) -> u32 { + return if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 }; + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/needless_return.stderr b/src/tools/clippy/tests/ui/needless_return.stderr index 52eabf6e1..87d0cd3e1 100644 --- a/src/tools/clippy/tests/ui/needless_return.stderr +++ b/src/tools/clippy/tests/ui/needless_return.stderr @@ -16,7 +16,23 @@ LL | return true; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:36:9 + --> $DIR/needless_return.rs:36:5 + | +LL | return true;;; + | ^^^^^^^^^^^ + | + = help: remove `return` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:41:5 + | +LL | return true;; ; ; + | ^^^^^^^^^^^ + | + = help: remove `return` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:46:9 | LL | return true; | ^^^^^^^^^^^ @@ -24,7 +40,7 @@ LL | return true; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:38:9 + --> $DIR/needless_return.rs:48:9 | LL | return false; | ^^^^^^^^^^^^ @@ -32,7 +48,7 @@ LL | return false; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:44:17 + --> $DIR/needless_return.rs:54:17 | LL | true => return false, | ^^^^^^^^^^^^ @@ -40,7 +56,7 @@ LL | true => return false, = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:46:13 + --> $DIR/needless_return.rs:56:13 | LL | return true; | ^^^^^^^^^^^ @@ -48,7 +64,7 @@ LL | return true; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:53:9 + --> $DIR/needless_return.rs:63:9 | LL | return true; | ^^^^^^^^^^^ @@ -56,7 +72,7 @@ LL | return true; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:55:16 + --> $DIR/needless_return.rs:65:16 | LL | let _ = || return true; | ^^^^^^^^^^^ @@ -64,7 +80,7 @@ LL | let _ = || return true; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:59:5 + --> $DIR/needless_return.rs:69:5 | LL | return the_answer!(); | ^^^^^^^^^^^^^^^^^^^^ @@ -72,7 +88,7 @@ LL | return the_answer!(); = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:62:21 + --> $DIR/needless_return.rs:72:21 | LL | fn test_void_fun() { | _____________________^ @@ -82,7 +98,7 @@ LL | | return; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:67:11 + --> $DIR/needless_return.rs:77:11 | LL | if b { | ___________^ @@ -92,7 +108,7 @@ LL | | return; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:69:13 + --> $DIR/needless_return.rs:79:13 | LL | } else { | _____________^ @@ -102,7 +118,7 @@ LL | | return; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:77:14 + --> $DIR/needless_return.rs:87:14 | LL | _ => return, | ^^^^^^ @@ -110,7 +126,7 @@ LL | _ => return, = help: replace `return` with a unit value error: unneeded `return` statement - --> $DIR/needless_return.rs:85:24 + --> $DIR/needless_return.rs:95:24 | LL | let _ = 42; | ________________________^ @@ -120,7 +136,7 @@ LL | | return; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:88:14 + --> $DIR/needless_return.rs:98:14 | LL | _ => return, | ^^^^^^ @@ -128,7 +144,7 @@ LL | _ => return, = help: replace `return` with a unit value error: unneeded `return` statement - --> $DIR/needless_return.rs:101:9 + --> $DIR/needless_return.rs:111:9 | LL | return String::from("test"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -136,7 +152,7 @@ LL | return String::from("test"); = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:103:9 + --> $DIR/needless_return.rs:113:9 | LL | return String::new(); | ^^^^^^^^^^^^^^^^^^^^ @@ -144,7 +160,7 @@ LL | return String::new(); = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:125:32 + --> $DIR/needless_return.rs:135:32 | LL | bar.unwrap_or_else(|_| return) | ^^^^^^ @@ -152,7 +168,7 @@ LL | bar.unwrap_or_else(|_| return) = help: replace `return` with an empty block error: unneeded `return` statement - --> $DIR/needless_return.rs:129:21 + --> $DIR/needless_return.rs:139:21 | LL | let _ = || { | _____________________^ @@ -162,7 +178,7 @@ LL | | return; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:132:20 + --> $DIR/needless_return.rs:142:20 | LL | let _ = || return; | ^^^^^^ @@ -170,7 +186,7 @@ LL | let _ = || return; = help: replace `return` with an empty block error: unneeded `return` statement - --> $DIR/needless_return.rs:138:32 + --> $DIR/needless_return.rs:148:32 | LL | res.unwrap_or_else(|_| return Foo) | ^^^^^^^^^^ @@ -178,7 +194,7 @@ LL | res.unwrap_or_else(|_| return Foo) = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:147:5 + --> $DIR/needless_return.rs:157:5 | LL | return true; | ^^^^^^^^^^^ @@ -186,7 +202,7 @@ LL | return true; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:151:5 + --> $DIR/needless_return.rs:161:5 | LL | return true; | ^^^^^^^^^^^ @@ -194,7 +210,7 @@ LL | return true; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:156:9 + --> $DIR/needless_return.rs:166:9 | LL | return true; | ^^^^^^^^^^^ @@ -202,7 +218,7 @@ LL | return true; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:158:9 + --> $DIR/needless_return.rs:168:9 | LL | return false; | ^^^^^^^^^^^^ @@ -210,7 +226,7 @@ LL | return false; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:164:17 + --> $DIR/needless_return.rs:174:17 | LL | true => return false, | ^^^^^^^^^^^^ @@ -218,7 +234,7 @@ LL | true => return false, = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:166:13 + --> $DIR/needless_return.rs:176:13 | LL | return true; | ^^^^^^^^^^^ @@ -226,7 +242,7 @@ LL | return true; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:173:9 + --> $DIR/needless_return.rs:183:9 | LL | return true; | ^^^^^^^^^^^ @@ -234,7 +250,7 @@ LL | return true; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:175:16 + --> $DIR/needless_return.rs:185:16 | LL | let _ = || return true; | ^^^^^^^^^^^ @@ -242,7 +258,7 @@ LL | let _ = || return true; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:179:5 + --> $DIR/needless_return.rs:189:5 | LL | return the_answer!(); | ^^^^^^^^^^^^^^^^^^^^ @@ -250,7 +266,7 @@ LL | return the_answer!(); = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:182:33 + --> $DIR/needless_return.rs:192:33 | LL | async fn async_test_void_fun() { | _________________________________^ @@ -260,7 +276,7 @@ LL | | return; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:187:11 + --> $DIR/needless_return.rs:197:11 | LL | if b { | ___________^ @@ -270,7 +286,7 @@ LL | | return; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:189:13 + --> $DIR/needless_return.rs:199:13 | LL | } else { | _____________^ @@ -280,7 +296,7 @@ LL | | return; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:197:14 + --> $DIR/needless_return.rs:207:14 | LL | _ => return, | ^^^^^^ @@ -288,7 +304,7 @@ LL | _ => return, = help: replace `return` with a unit value error: unneeded `return` statement - --> $DIR/needless_return.rs:210:9 + --> $DIR/needless_return.rs:220:9 | LL | return String::from("test"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -296,7 +312,7 @@ LL | return String::from("test"); = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:212:9 + --> $DIR/needless_return.rs:222:9 | LL | return String::new(); | ^^^^^^^^^^^^^^^^^^^^ @@ -304,7 +320,7 @@ LL | return String::new(); = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:228:5 + --> $DIR/needless_return.rs:238:5 | LL | return format!("Hello {}", "world!"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -312,7 +328,7 @@ LL | return format!("Hello {}", "world!"); = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:239:9 + --> $DIR/needless_return.rs:249:9 | LL | return true; | ^^^^^^^^^^^ @@ -320,7 +336,7 @@ LL | return true; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:241:9 + --> $DIR/needless_return.rs:251:9 | LL | return false; | ^^^^^^^^^^^^ @@ -328,7 +344,7 @@ LL | return false; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:248:13 + --> $DIR/needless_return.rs:258:13 | LL | return 10; | ^^^^^^^^^ @@ -336,7 +352,7 @@ LL | return 10; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:251:13 + --> $DIR/needless_return.rs:261:13 | LL | return 100; | ^^^^^^^^^^ @@ -344,7 +360,7 @@ LL | return 100; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:259:9 + --> $DIR/needless_return.rs:269:9 | LL | return 0; | ^^^^^^^^ @@ -352,7 +368,7 @@ LL | return 0; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:266:13 + --> $DIR/needless_return.rs:276:13 | LL | return *(x as *const isize); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -360,7 +376,7 @@ LL | return *(x as *const isize); = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:268:13 + --> $DIR/needless_return.rs:278:13 | LL | return !*(x as *const isize); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -368,7 +384,7 @@ LL | return !*(x as *const isize); = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:275:20 + --> $DIR/needless_return.rs:285:20 | LL | let _ = 42; | ____________________^ @@ -379,7 +395,7 @@ LL | | return; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:282:20 + --> $DIR/needless_return.rs:292:20 | LL | let _ = 42; return; | ^^^^^^^ @@ -387,7 +403,7 @@ LL | let _ = 42; return; = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:294:9 + --> $DIR/needless_return.rs:304:9 | LL | return Ok(format!("ok!")); | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -395,12 +411,28 @@ LL | return Ok(format!("ok!")); = help: remove `return` error: unneeded `return` statement - --> $DIR/needless_return.rs:296:9 + --> $DIR/needless_return.rs:306:9 | LL | return Err(format!("err!")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: remove `return` -error: aborting due to 48 previous errors +error: unneeded `return` statement + --> $DIR/needless_return.rs:312:9 + | +LL | return if true { 1 } else { 2 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `return` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:316:9 + | +LL | return if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `return` and wrap the sequence with parentheses + +error: aborting due to 52 previous errors diff --git a/src/tools/clippy/tests/ui/never_loop.rs b/src/tools/clippy/tests/ui/never_loop.rs index 28e8f459d..29821ff96 100644 --- a/src/tools/clippy/tests/ui/never_loop.rs +++ b/src/tools/clippy/tests/ui/never_loop.rs @@ -250,6 +250,51 @@ pub fn test20() { } } +pub fn test21() { + loop { + 'a: { + {} + break 'a; + } + } +} + +// Issue 10304: code after break from block was not considered +// unreachable code and was considered for further analysis of +// whether the loop would ever be executed or not. +pub fn test22() { + for _ in 0..10 { + 'block: { + break 'block; + return; + } + println!("looped"); + } +} + +pub fn test23() { + for _ in 0..10 { + 'block: { + for _ in 0..20 { + break 'block; + } + } + println!("looped"); + } +} + +pub fn test24() { + 'a: for _ in 0..10 { + 'b: { + let x = Some(1); + match x { + None => break 'a, + Some(_) => break 'b, + } + } + } +} + fn main() { test1(); test2(); diff --git a/src/tools/clippy/tests/ui/never_loop.stderr b/src/tools/clippy/tests/ui/never_loop.stderr index b7029bf8b..704d44864 100644 --- a/src/tools/clippy/tests/ui/never_loop.stderr +++ b/src/tools/clippy/tests/ui/never_loop.stderr @@ -126,5 +126,18 @@ LL | | } LL | | } | |_____^ -error: aborting due to 11 previous errors +error: this loop never actually loops + --> $DIR/never_loop.rs:278:13 + | +LL | / for _ in 0..20 { +LL | | break 'block; +LL | | } + | |_____________^ + | +help: if you need the first element of the iterator, try writing + | +LL | if let Some(_) = (0..20).next() { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: aborting due to 12 previous errors diff --git a/src/tools/clippy/tests/ui/new_without_default.rs b/src/tools/clippy/tests/ui/new_without_default.rs index 65809023f..7803418cb 100644 --- a/src/tools/clippy/tests/ui/new_without_default.rs +++ b/src/tools/clippy/tests/ui/new_without_default.rs @@ -1,4 +1,9 @@ -#![allow(dead_code, clippy::missing_safety_doc, clippy::extra_unused_lifetimes)] +#![allow( + dead_code, + clippy::missing_safety_doc, + clippy::extra_unused_lifetimes, + clippy::extra_unused_type_parameters +)] #![warn(clippy::new_without_default)] pub struct Foo; diff --git a/src/tools/clippy/tests/ui/new_without_default.stderr b/src/tools/clippy/tests/ui/new_without_default.stderr index 212a69ab9..583dd327d 100644 --- a/src/tools/clippy/tests/ui/new_without_default.stderr +++ b/src/tools/clippy/tests/ui/new_without_default.stderr @@ -1,5 +1,5 @@ error: you should consider adding a `Default` implementation for `Foo` - --> $DIR/new_without_default.rs:7:5 + --> $DIR/new_without_default.rs:12:5 | LL | / pub fn new() -> Foo { LL | | Foo @@ -17,7 +17,7 @@ LL + } | error: you should consider adding a `Default` implementation for `Bar` - --> $DIR/new_without_default.rs:15:5 + --> $DIR/new_without_default.rs:20:5 | LL | / pub fn new() -> Self { LL | | Bar @@ -34,7 +34,7 @@ LL + } | error: you should consider adding a `Default` implementation for `LtKo<'c>` - --> $DIR/new_without_default.rs:79:5 + --> $DIR/new_without_default.rs:84:5 | LL | / pub fn new() -> LtKo<'c> { LL | | unimplemented!() @@ -51,7 +51,7 @@ LL + } | error: you should consider adding a `Default` implementation for `NewNotEqualToDerive` - --> $DIR/new_without_default.rs:172:5 + --> $DIR/new_without_default.rs:177:5 | LL | / pub fn new() -> Self { LL | | NewNotEqualToDerive { foo: 1 } @@ -68,7 +68,7 @@ LL + } | error: you should consider adding a `Default` implementation for `FooGenerics<T>` - --> $DIR/new_without_default.rs:180:5 + --> $DIR/new_without_default.rs:185:5 | LL | / pub fn new() -> Self { LL | | Self(Default::default()) @@ -85,7 +85,7 @@ LL + } | error: you should consider adding a `Default` implementation for `BarGenerics<T>` - --> $DIR/new_without_default.rs:187:5 + --> $DIR/new_without_default.rs:192:5 | LL | / pub fn new() -> Self { LL | | Self(Default::default()) @@ -102,7 +102,7 @@ LL + } | error: you should consider adding a `Default` implementation for `Foo<T>` - --> $DIR/new_without_default.rs:198:9 + --> $DIR/new_without_default.rs:203:9 | LL | / pub fn new() -> Self { LL | | todo!() diff --git a/src/tools/clippy/tests/ui/no_mangle_with_rust_abi.fixed b/src/tools/clippy/tests/ui/no_mangle_with_rust_abi.fixed new file mode 100644 index 000000000..d18dec22a --- /dev/null +++ b/src/tools/clippy/tests/ui/no_mangle_with_rust_abi.fixed @@ -0,0 +1,48 @@ +// run-rustfix + +#![allow(unused)] +#![warn(clippy::no_mangle_with_rust_abi)] + +#[no_mangle] +extern "C" fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} + +#[no_mangle] +pub extern "C" fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} + +/// # Safety +/// This function shouldn't be called unless the horsemen are ready +#[no_mangle] +pub unsafe extern "C" fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} + +/// # Safety +/// This function shouldn't be called unless the horsemen are ready +#[no_mangle] +unsafe extern "C" fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} + +#[no_mangle] +extern "C" fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( + arg_one: u32, + arg_two: usize, +) -> u32 { + 0 +} + +// Must not run on functions that explicitly opt in to Rust ABI with `extern "Rust"` +#[no_mangle] +#[rustfmt::skip] +extern "Rust" fn rust_abi_fn_explicit_opt_in(arg_one: u32, arg_two: usize) {} + +fn rust_abi_fn_again(arg_one: u32, arg_two: usize) {} + +#[no_mangle] +extern "C" fn c_abi_fn(arg_one: u32, arg_two: usize) {} + +extern "C" fn c_abi_fn_again(arg_one: u32, arg_two: usize) {} + +extern "C" { + fn c_abi_in_block(arg_one: u32, arg_two: usize); +} + +fn main() { + // test code goes here +} diff --git a/src/tools/clippy/tests/ui/no_mangle_with_rust_abi.rs b/src/tools/clippy/tests/ui/no_mangle_with_rust_abi.rs new file mode 100644 index 000000000..481e1b6d9 --- /dev/null +++ b/src/tools/clippy/tests/ui/no_mangle_with_rust_abi.rs @@ -0,0 +1,48 @@ +// run-rustfix + +#![allow(unused)] +#![warn(clippy::no_mangle_with_rust_abi)] + +#[no_mangle] +fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} + +#[no_mangle] +pub fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} + +/// # Safety +/// This function shouldn't be called unless the horsemen are ready +#[no_mangle] +pub unsafe fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} + +/// # Safety +/// This function shouldn't be called unless the horsemen are ready +#[no_mangle] +unsafe fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} + +#[no_mangle] +fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( + arg_one: u32, + arg_two: usize, +) -> u32 { + 0 +} + +// Must not run on functions that explicitly opt in to Rust ABI with `extern "Rust"` +#[no_mangle] +#[rustfmt::skip] +extern "Rust" fn rust_abi_fn_explicit_opt_in(arg_one: u32, arg_two: usize) {} + +fn rust_abi_fn_again(arg_one: u32, arg_two: usize) {} + +#[no_mangle] +extern "C" fn c_abi_fn(arg_one: u32, arg_two: usize) {} + +extern "C" fn c_abi_fn_again(arg_one: u32, arg_two: usize) {} + +extern "C" { + fn c_abi_in_block(arg_one: u32, arg_two: usize); +} + +fn main() { + // test code goes here +} diff --git a/src/tools/clippy/tests/ui/no_mangle_with_rust_abi.stderr b/src/tools/clippy/tests/ui/no_mangle_with_rust_abi.stderr new file mode 100644 index 000000000..71517d318 --- /dev/null +++ b/src/tools/clippy/tests/ui/no_mangle_with_rust_abi.stderr @@ -0,0 +1,45 @@ +error: attribute #[no_mangle] set on a Rust ABI function + --> $DIR/no_mangle_with_rust_abi.rs:7:1 + | +LL | fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `extern "C" fn rust_abi_fn_one(arg_one: u32, arg_two: usize)` + | + = note: `-D clippy::no-mangle-with-rust-abi` implied by `-D warnings` + +error: attribute #[no_mangle] set on a Rust ABI function + --> $DIR/no_mangle_with_rust_abi.rs:10:1 + | +LL | pub fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `pub extern "C" fn rust_abi_fn_two(arg_one: u32, arg_two: usize)` + +error: attribute #[no_mangle] set on a Rust ABI function + --> $DIR/no_mangle_with_rust_abi.rs:15:1 + | +LL | pub unsafe fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `pub unsafe extern "C" fn rust_abi_fn_three(arg_one: u32, arg_two: usize)` + +error: attribute #[no_mangle] set on a Rust ABI function + --> $DIR/no_mangle_with_rust_abi.rs:20:1 + | +LL | unsafe fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unsafe extern "C" fn rust_abi_fn_four(arg_one: u32, arg_two: usize)` + +error: attribute #[no_mangle] set on a Rust ABI function + --> $DIR/no_mangle_with_rust_abi.rs:23:1 + | +LL | / fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( +LL | | arg_one: u32, +LL | | arg_two: usize, +LL | | ) -> u32 { + | |________^ + | +help: try + | +LL + extern "C" fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( +LL + arg_one: u32, +LL + arg_two: usize, +LL ~ ) -> u32 { + | + +error: aborting due to 5 previous errors + diff --git a/src/tools/clippy/tests/ui/question_mark_used.rs b/src/tools/clippy/tests/ui/question_mark_used.rs new file mode 100644 index 000000000..8c3ef7896 --- /dev/null +++ b/src/tools/clippy/tests/ui/question_mark_used.rs @@ -0,0 +1,15 @@ +// non rustfixable +#![allow(unreachable_code)] +#![allow(dead_code)] +#![warn(clippy::question_mark_used)] + +fn other_function() -> Option<i32> { + Some(32) +} + +fn my_function() -> Option<i32> { + other_function()?; + None +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/question_mark_used.stderr b/src/tools/clippy/tests/ui/question_mark_used.stderr new file mode 100644 index 000000000..8b5fcbcdb --- /dev/null +++ b/src/tools/clippy/tests/ui/question_mark_used.stderr @@ -0,0 +1,11 @@ +error: question mark operator was used + --> $DIR/question_mark_used.rs:11:5 + | +LL | other_function()?; + | ^^^^^^^^^^^^^^^^^ + | + = help: consider using a custom macro or match expression + = note: `-D clippy::question-mark-used` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/src/tools/clippy/tests/ui/redundant_field_names.fixed b/src/tools/clippy/tests/ui/redundant_field_names.fixed index ec7f8ae92..276266a2d 100644 --- a/src/tools/clippy/tests/ui/redundant_field_names.fixed +++ b/src/tools/clippy/tests/ui/redundant_field_names.fixed @@ -1,7 +1,7 @@ // run-rustfix #![warn(clippy::redundant_field_names)] -#![allow(clippy::no_effect, dead_code, unused_variables)] +#![allow(clippy::extra_unused_type_parameters, clippy::no_effect, dead_code, unused_variables)] #[macro_use] extern crate derive_new; diff --git a/src/tools/clippy/tests/ui/redundant_field_names.rs b/src/tools/clippy/tests/ui/redundant_field_names.rs index 73122016c..f674141c1 100644 --- a/src/tools/clippy/tests/ui/redundant_field_names.rs +++ b/src/tools/clippy/tests/ui/redundant_field_names.rs @@ -1,7 +1,7 @@ // run-rustfix #![warn(clippy::redundant_field_names)] -#![allow(clippy::no_effect, dead_code, unused_variables)] +#![allow(clippy::extra_unused_type_parameters, clippy::no_effect, dead_code, unused_variables)] #[macro_use] extern crate derive_new; diff --git a/src/tools/clippy/tests/ui/regex.rs b/src/tools/clippy/tests/ui/regex.rs index f0e1a8128..ab8ac97a0 100644 --- a/src/tools/clippy/tests/ui/regex.rs +++ b/src/tools/clippy/tests/ui/regex.rs @@ -36,6 +36,10 @@ fn syntax_error() { let raw_string_error = Regex::new(r"[...\/...]"); let raw_string_error = Regex::new(r#"[...\/...]"#); + + let escaped_string_span = Regex::new("\\b\\c"); + + let aux_span = Regex::new("(?ixi)"); } fn trivial_regex() { diff --git a/src/tools/clippy/tests/ui/regex.stderr b/src/tools/clippy/tests/ui/regex.stderr index 2424644c6..c2440f39e 100644 --- a/src/tools/clippy/tests/ui/regex.stderr +++ b/src/tools/clippy/tests/ui/regex.stderr @@ -29,7 +29,10 @@ error: regex syntax error: invalid character class range, the start must be <= t LL | let some_unicode = Regex::new("[é-è]"); | ^^^ -error: regex syntax error on position 0: unclosed group +error: regex parse error: + ( + ^ + error: unclosed group --> $DIR/regex.rs:18:33 | LL | let some_regex = Regex::new(OPENING_PAREN); @@ -43,25 +46,37 @@ LL | let binary_pipe_in_wrong_position = BRegex::new("|"); | = help: the regex is unlikely to be useful as it is -error: regex syntax error on position 0: unclosed group +error: regex parse error: + ( + ^ + error: unclosed group --> $DIR/regex.rs:21:41 | LL | let some_binary_regex = BRegex::new(OPENING_PAREN); | ^^^^^^^^^^^^^ -error: regex syntax error on position 0: unclosed group +error: regex parse error: + ( + ^ + error: unclosed group --> $DIR/regex.rs:22:56 | LL | let some_binary_regex_builder = BRegexBuilder::new(OPENING_PAREN); | ^^^^^^^^^^^^^ -error: regex syntax error on position 0: unclosed group +error: regex parse error: + ( + ^ + error: unclosed group --> $DIR/regex.rs:34:37 | LL | let set_error = RegexSet::new(&[OPENING_PAREN, r"[a-z]+/.(com|org|net)"]); | ^^^^^^^^^^^^^ -error: regex syntax error on position 0: unclosed group +error: regex parse error: + ( + ^ + error: unclosed group --> $DIR/regex.rs:35:39 | LL | let bset_error = BRegexSet::new(&[OPENING_PAREN, r"[a-z]+/.(com|org|net)"]); @@ -79,8 +94,25 @@ error: regex syntax error: unrecognized escape sequence LL | let raw_string_error = Regex::new(r#"[...//...]"#); | ^^ +error: regex parse error: + /b/c + ^^ + error: unrecognized escape sequence + --> $DIR/regex.rs:40:42 + | +LL | let escaped_string_span = Regex::new("/b/c"); + | ^^^^^^^^ + | + = help: consider using a raw string literal: `r".."` + +error: regex syntax error: duplicate flag + --> $DIR/regex.rs:42:34 + | +LL | let aux_span = Regex::new("(?ixi)"); + | ^ ^ + error: trivial regex - --> $DIR/regex.rs:42:33 + --> $DIR/regex.rs:46:33 | LL | let trivial_eq = Regex::new("^foobar$"); | ^^^^^^^^^^ @@ -88,7 +120,7 @@ LL | let trivial_eq = Regex::new("^foobar$"); = help: consider using `==` on `str`s error: trivial regex - --> $DIR/regex.rs:44:48 + --> $DIR/regex.rs:48:48 | LL | let trivial_eq_builder = RegexBuilder::new("^foobar$"); | ^^^^^^^^^^ @@ -96,7 +128,7 @@ LL | let trivial_eq_builder = RegexBuilder::new("^foobar$"); = help: consider using `==` on `str`s error: trivial regex - --> $DIR/regex.rs:46:42 + --> $DIR/regex.rs:50:42 | LL | let trivial_starts_with = Regex::new("^foobar"); | ^^^^^^^^^ @@ -104,7 +136,7 @@ LL | let trivial_starts_with = Regex::new("^foobar"); = help: consider using `str::starts_with` error: trivial regex - --> $DIR/regex.rs:48:40 + --> $DIR/regex.rs:52:40 | LL | let trivial_ends_with = Regex::new("foobar$"); | ^^^^^^^^^ @@ -112,7 +144,7 @@ LL | let trivial_ends_with = Regex::new("foobar$"); = help: consider using `str::ends_with` error: trivial regex - --> $DIR/regex.rs:50:39 + --> $DIR/regex.rs:54:39 | LL | let trivial_contains = Regex::new("foobar"); | ^^^^^^^^ @@ -120,7 +152,7 @@ LL | let trivial_contains = Regex::new("foobar"); = help: consider using `str::contains` error: trivial regex - --> $DIR/regex.rs:52:39 + --> $DIR/regex.rs:56:39 | LL | let trivial_contains = Regex::new(NOT_A_REAL_REGEX); | ^^^^^^^^^^^^^^^^ @@ -128,7 +160,7 @@ LL | let trivial_contains = Regex::new(NOT_A_REAL_REGEX); = help: consider using `str::contains` error: trivial regex - --> $DIR/regex.rs:54:40 + --> $DIR/regex.rs:58:40 | LL | let trivial_backslash = Regex::new("a/.b"); | ^^^^^^^ @@ -136,7 +168,7 @@ LL | let trivial_backslash = Regex::new("a/.b"); = help: consider using `str::contains` error: trivial regex - --> $DIR/regex.rs:57:36 + --> $DIR/regex.rs:61:36 | LL | let trivial_empty = Regex::new(""); | ^^ @@ -144,7 +176,7 @@ LL | let trivial_empty = Regex::new(""); = help: the regex is unlikely to be useful as it is error: trivial regex - --> $DIR/regex.rs:59:36 + --> $DIR/regex.rs:63:36 | LL | let trivial_empty = Regex::new("^"); | ^^^ @@ -152,7 +184,7 @@ LL | let trivial_empty = Regex::new("^"); = help: the regex is unlikely to be useful as it is error: trivial regex - --> $DIR/regex.rs:61:36 + --> $DIR/regex.rs:65:36 | LL | let trivial_empty = Regex::new("^$"); | ^^^^ @@ -160,12 +192,12 @@ LL | let trivial_empty = Regex::new("^$"); = help: consider using `str::is_empty` error: trivial regex - --> $DIR/regex.rs:63:44 + --> $DIR/regex.rs:67:44 | LL | let binary_trivial_empty = BRegex::new("^$"); | ^^^^ | = help: consider using `str::is_empty` -error: aborting due to 23 previous errors +error: aborting due to 25 previous errors diff --git a/src/tools/clippy/tests/ui/seek_to_start_instead_of_rewind.fixed b/src/tools/clippy/tests/ui/seek_to_start_instead_of_rewind.fixed index 713cff604..dc24d447c 100644 --- a/src/tools/clippy/tests/ui/seek_to_start_instead_of_rewind.fixed +++ b/src/tools/clippy/tests/ui/seek_to_start_instead_of_rewind.fixed @@ -26,7 +26,7 @@ fn seek_to_start_false_method(t: &mut StructWithSeekMethod) { // This should NOT trigger clippy warning because // StructWithSeekMethod does not implement std::io::Seek; -fn seek_to_start_method_owned_false<T>(mut t: StructWithSeekMethod) { +fn seek_to_start_method_owned_false(mut t: StructWithSeekMethod) { t.seek(SeekFrom::Start(0)); } @@ -38,7 +38,7 @@ fn seek_to_start_false_trait(t: &mut StructWithSeekTrait) { // This should NOT trigger clippy warning because // StructWithSeekMethod does not implement std::io::Seek; -fn seek_to_start_false_trait_owned<T>(mut t: StructWithSeekTrait) { +fn seek_to_start_false_trait_owned(mut t: StructWithSeekTrait) { t.seek(SeekFrom::Start(0)); } diff --git a/src/tools/clippy/tests/ui/seek_to_start_instead_of_rewind.rs b/src/tools/clippy/tests/ui/seek_to_start_instead_of_rewind.rs index 467003a1a..4adde2c40 100644 --- a/src/tools/clippy/tests/ui/seek_to_start_instead_of_rewind.rs +++ b/src/tools/clippy/tests/ui/seek_to_start_instead_of_rewind.rs @@ -26,7 +26,7 @@ fn seek_to_start_false_method(t: &mut StructWithSeekMethod) { // This should NOT trigger clippy warning because // StructWithSeekMethod does not implement std::io::Seek; -fn seek_to_start_method_owned_false<T>(mut t: StructWithSeekMethod) { +fn seek_to_start_method_owned_false(mut t: StructWithSeekMethod) { t.seek(SeekFrom::Start(0)); } @@ -38,7 +38,7 @@ fn seek_to_start_false_trait(t: &mut StructWithSeekTrait) { // This should NOT trigger clippy warning because // StructWithSeekMethod does not implement std::io::Seek; -fn seek_to_start_false_trait_owned<T>(mut t: StructWithSeekTrait) { +fn seek_to_start_false_trait_owned(mut t: StructWithSeekTrait) { t.seek(SeekFrom::Start(0)); } diff --git a/src/tools/clippy/tests/ui/significant_drop_tightening.fixed b/src/tools/clippy/tests/ui/significant_drop_tightening.fixed new file mode 100644 index 000000000..da998c610 --- /dev/null +++ b/src/tools/clippy/tests/ui/significant_drop_tightening.fixed @@ -0,0 +1,84 @@ +// run-rustfix + +#![warn(clippy::significant_drop_tightening)] + +use std::sync::Mutex; + +pub fn complex_return_triggers_the_lint() -> i32 { + fn foo() -> i32 { + 1 + } + let mutex = Mutex::new(1); + let lock = mutex.lock().unwrap(); + let _ = *lock; + let _ = *lock; + drop(lock); + foo() +} + +pub fn path_return_can_be_ignored() -> i32 { + let mutex = Mutex::new(1); + let lock = mutex.lock().unwrap(); + let rslt = *lock; + let _ = *lock; + rslt +} + +pub fn post_bindings_can_be_ignored() { + let mutex = Mutex::new(1); + let lock = mutex.lock().unwrap(); + let rslt = *lock; + let another = rslt; + let _ = another; +} + +pub fn unnecessary_contention_with_multiple_owned_results() { + { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let _ = lock.abs(); + let _ = lock.is_positive(); + } + + { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let rslt0 = lock.abs(); + let rslt1 = lock.is_positive(); + drop(lock); + do_heavy_computation_that_takes_time((rslt0, rslt1)); + } +} + +pub fn unnecessary_contention_with_single_owned_results() { + { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let _ = lock.abs(); + } + { + let mutex = Mutex::new(vec![1i32]); + let mut lock = mutex.lock().unwrap(); + lock.clear(); + } + + { + let mutex = Mutex::new(1i32); + + let rslt0 = mutex.lock().unwrap().abs(); + + do_heavy_computation_that_takes_time(rslt0); + } + { + let mutex = Mutex::new(vec![1i32]); + + mutex.lock().unwrap().clear(); + + do_heavy_computation_that_takes_time(()); + } +} + +// Marker used for illustration purposes. +pub fn do_heavy_computation_that_takes_time<T>(_: T) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/significant_drop_tightening.rs b/src/tools/clippy/tests/ui/significant_drop_tightening.rs new file mode 100644 index 000000000..83823f95f --- /dev/null +++ b/src/tools/clippy/tests/ui/significant_drop_tightening.rs @@ -0,0 +1,80 @@ +// run-rustfix + +#![warn(clippy::significant_drop_tightening)] + +use std::sync::Mutex; + +pub fn complex_return_triggers_the_lint() -> i32 { + fn foo() -> i32 { + 1 + } + let mutex = Mutex::new(1); + let lock = mutex.lock().unwrap(); + let _ = *lock; + let _ = *lock; + foo() +} + +pub fn path_return_can_be_ignored() -> i32 { + let mutex = Mutex::new(1); + let lock = mutex.lock().unwrap(); + let rslt = *lock; + let _ = *lock; + rslt +} + +pub fn post_bindings_can_be_ignored() { + let mutex = Mutex::new(1); + let lock = mutex.lock().unwrap(); + let rslt = *lock; + let another = rslt; + let _ = another; +} + +pub fn unnecessary_contention_with_multiple_owned_results() { + { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let _ = lock.abs(); + let _ = lock.is_positive(); + } + + { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let rslt0 = lock.abs(); + let rslt1 = lock.is_positive(); + do_heavy_computation_that_takes_time((rslt0, rslt1)); + } +} + +pub fn unnecessary_contention_with_single_owned_results() { + { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let _ = lock.abs(); + } + { + let mutex = Mutex::new(vec![1i32]); + let mut lock = mutex.lock().unwrap(); + lock.clear(); + } + + { + let mutex = Mutex::new(1i32); + let lock = mutex.lock().unwrap(); + let rslt0 = lock.abs(); + do_heavy_computation_that_takes_time(rslt0); + } + { + let mutex = Mutex::new(vec![1i32]); + let mut lock = mutex.lock().unwrap(); + lock.clear(); + do_heavy_computation_that_takes_time(()); + } +} + +// Marker used for illustration purposes. +pub fn do_heavy_computation_that_takes_time<T>(_: T) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/significant_drop_tightening.stderr b/src/tools/clippy/tests/ui/significant_drop_tightening.stderr new file mode 100644 index 000000000..ab8ce356e --- /dev/null +++ b/src/tools/clippy/tests/ui/significant_drop_tightening.stderr @@ -0,0 +1,94 @@ +error: temporary with significant `Drop` can be early dropped + --> $DIR/significant_drop_tightening.rs:12:9 + | +LL | pub fn complex_return_triggers_the_lint() -> i32 { + | __________________________________________________- +LL | | fn foo() -> i32 { +LL | | 1 +LL | | } +LL | | let mutex = Mutex::new(1); +LL | | let lock = mutex.lock().unwrap(); + | | ^^^^ +... | +LL | | foo() +LL | | } + | |_- temporary `lock` is currently being dropped at the end of its contained scope + | + = note: this might lead to unnecessary resource contention + = note: `-D clippy::significant-drop-tightening` implied by `-D warnings` +help: drop the temporary after the end of its last usage + | +LL ~ let _ = *lock; +LL + drop(lock); + | + +error: temporary with significant `Drop` can be early dropped + --> $DIR/significant_drop_tightening.rs:44:13 + | +LL | / { +LL | | let mutex = Mutex::new(1i32); +LL | | let lock = mutex.lock().unwrap(); + | | ^^^^ +LL | | let rslt0 = lock.abs(); +LL | | let rslt1 = lock.is_positive(); +LL | | do_heavy_computation_that_takes_time((rslt0, rslt1)); +LL | | } + | |_____- temporary `lock` is currently being dropped at the end of its contained scope + | + = note: this might lead to unnecessary resource contention +help: drop the temporary after the end of its last usage + | +LL ~ let rslt1 = lock.is_positive(); +LL + drop(lock); + | + +error: temporary with significant `Drop` can be early dropped + --> $DIR/significant_drop_tightening.rs:65:13 + | +LL | / { +LL | | let mutex = Mutex::new(1i32); +LL | | let lock = mutex.lock().unwrap(); + | | ^^^^ +LL | | let rslt0 = lock.abs(); +LL | | do_heavy_computation_that_takes_time(rslt0); +LL | | } + | |_____- temporary `lock` is currently being dropped at the end of its contained scope + | + = note: this might lead to unnecessary resource contention +help: merge the temporary construction with its single usage + | +LL ~ +LL + let rslt0 = mutex.lock().unwrap().abs(); + | +help: remove separated single usage + | +LL - let rslt0 = lock.abs(); +LL + + | + +error: temporary with significant `Drop` can be early dropped + --> $DIR/significant_drop_tightening.rs:71:17 + | +LL | / { +LL | | let mutex = Mutex::new(vec![1i32]); +LL | | let mut lock = mutex.lock().unwrap(); + | | ^^^^ +LL | | lock.clear(); +LL | | do_heavy_computation_that_takes_time(()); +LL | | } + | |_____- temporary `lock` is currently being dropped at the end of its contained scope + | + = note: this might lead to unnecessary resource contention +help: merge the temporary construction with its single usage + | +LL ~ +LL + mutex.lock().unwrap().clear(); + | +help: remove separated single usage + | +LL - lock.clear(); +LL + + | + +error: aborting due to 4 previous errors + diff --git a/src/tools/clippy/tests/ui/suspicious_command_arg_space.rs b/src/tools/clippy/tests/ui/suspicious_command_arg_space.rs new file mode 100644 index 000000000..bdc6113a2 --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_command_arg_space.rs @@ -0,0 +1,10 @@ +fn main() { + // Things it should warn about: + std::process::Command::new("echo").arg("-n hello").spawn().unwrap(); + std::process::Command::new("cat").arg("--number file").spawn().unwrap(); + + // Things it should not warn about: + std::process::Command::new("echo").arg("hello world").spawn().unwrap(); + std::process::Command::new("a").arg("--fmt=%a %b %c").spawn().unwrap(); + std::process::Command::new("b").arg("-ldflags=-s -w").spawn().unwrap(); +} diff --git a/src/tools/clippy/tests/ui/suspicious_command_arg_space.stderr b/src/tools/clippy/tests/ui/suspicious_command_arg_space.stderr new file mode 100644 index 000000000..9bc0ca93a --- /dev/null +++ b/src/tools/clippy/tests/ui/suspicious_command_arg_space.stderr @@ -0,0 +1,25 @@ +error: single argument that looks like it should be multiple arguments + --> $DIR/suspicious_command_arg_space.rs:3:44 + | +LL | std::process::Command::new("echo").arg("-n hello").spawn().unwrap(); + | ^^^^^^^^^^ + | + = note: `-D clippy::suspicious-command-arg-space` implied by `-D warnings` +help: consider splitting the argument + | +LL | std::process::Command::new("echo").args(["-n", "hello"]).spawn().unwrap(); + | ~~~~ ~~~~~~~~~~~~~~~ + +error: single argument that looks like it should be multiple arguments + --> $DIR/suspicious_command_arg_space.rs:4:43 + | +LL | std::process::Command::new("cat").arg("--number file").spawn().unwrap(); + | ^^^^^^^^^^^^^^^ + | +help: consider splitting the argument + | +LL | std::process::Command::new("cat").args(["--number", "file"]).spawn().unwrap(); + | ~~~~ ~~~~~~~~~~~~~~~~~~~~ + +error: aborting due to 2 previous errors + diff --git a/src/tools/clippy/tests/ui/suspicious_to_owned.stderr b/src/tools/clippy/tests/ui/suspicious_to_owned.stderr index dec3f50d6..c4ec7aa88 100644 --- a/src/tools/clippy/tests/ui/suspicious_to_owned.stderr +++ b/src/tools/clippy/tests/ui/suspicious_to_owned.stderr @@ -2,27 +2,62 @@ error: this `to_owned` call clones the Cow<'_, str> itself and does not cause th --> $DIR/suspicious_to_owned.rs:16:13 | LL | let _ = cow.to_owned(); - | ^^^^^^^^^^^^^^ help: consider using, depending on intent: `cow.clone()` or `cow.into_owned()` + | ^^^^^^^^^^^^^^ | = note: `-D clippy::suspicious-to-owned` implied by `-D warnings` +help: depending on intent, either make the Cow an Owned variant + | +LL | let _ = cow.into_owned(); + | ~~~~~~~~~~~~~~~~ +help: or clone the Cow itself + | +LL | let _ = cow.clone(); + | ~~~~~~~~~~~ error: this `to_owned` call clones the Cow<'_, [char; 3]> itself and does not cause the Cow<'_, [char; 3]> contents to become owned --> $DIR/suspicious_to_owned.rs:26:13 | LL | let _ = cow.to_owned(); - | ^^^^^^^^^^^^^^ help: consider using, depending on intent: `cow.clone()` or `cow.into_owned()` + | ^^^^^^^^^^^^^^ + | +help: depending on intent, either make the Cow an Owned variant + | +LL | let _ = cow.into_owned(); + | ~~~~~~~~~~~~~~~~ +help: or clone the Cow itself + | +LL | let _ = cow.clone(); + | ~~~~~~~~~~~ error: this `to_owned` call clones the Cow<'_, Vec<char>> itself and does not cause the Cow<'_, Vec<char>> contents to become owned --> $DIR/suspicious_to_owned.rs:36:13 | LL | let _ = cow.to_owned(); - | ^^^^^^^^^^^^^^ help: consider using, depending on intent: `cow.clone()` or `cow.into_owned()` + | ^^^^^^^^^^^^^^ + | +help: depending on intent, either make the Cow an Owned variant + | +LL | let _ = cow.into_owned(); + | ~~~~~~~~~~~~~~~~ +help: or clone the Cow itself + | +LL | let _ = cow.clone(); + | ~~~~~~~~~~~ error: this `to_owned` call clones the Cow<'_, str> itself and does not cause the Cow<'_, str> contents to become owned --> $DIR/suspicious_to_owned.rs:46:13 | LL | let _ = cow.to_owned(); - | ^^^^^^^^^^^^^^ help: consider using, depending on intent: `cow.clone()` or `cow.into_owned()` + | ^^^^^^^^^^^^^^ + | +help: depending on intent, either make the Cow an Owned variant + | +LL | let _ = cow.into_owned(); + | ~~~~~~~~~~~~~~~~ +help: or clone the Cow itself + | +LL | let _ = cow.clone(); + | ~~~~~~~~~~~ error: implicitly cloning a `String` by calling `to_owned` on its dereferenced type --> $DIR/suspicious_to_owned.rs:60:13 diff --git a/src/tools/clippy/tests/ui/swap.fixed b/src/tools/clippy/tests/ui/swap.fixed index 805a2ba5a..fa89706a8 100644 --- a/src/tools/clippy/tests/ui/swap.fixed +++ b/src/tools/clippy/tests/ui/swap.fixed @@ -7,7 +7,8 @@ clippy::redundant_clone, redundant_semicolons, dead_code, - unused_assignments + unused_assignments, + unused_variables )] struct Foo(u32); @@ -121,6 +122,27 @@ fn main() { std::mem::swap(&mut c.0, &mut a); ; std::mem::swap(&mut c.0, &mut a); + + std::mem::swap(&mut a, &mut b); + + let mut c = 1; + let mut d = 2; + std::mem::swap(&mut d, &mut c); + + let mut b = 1; + std::mem::swap(&mut a, &mut b); + + let b = 1; + let a = 2; + + let t = b; + let b = a; + let a = t; + + let mut b = 1; + let mut a = 2; + + std::mem::swap(&mut b, &mut a); } fn issue_8154() { diff --git a/src/tools/clippy/tests/ui/swap.rs b/src/tools/clippy/tests/ui/swap.rs index a8c878479..ef8a81c83 100644 --- a/src/tools/clippy/tests/ui/swap.rs +++ b/src/tools/clippy/tests/ui/swap.rs @@ -7,7 +7,8 @@ clippy::redundant_clone, redundant_semicolons, dead_code, - unused_assignments + unused_assignments, + unused_variables )] struct Foo(u32); @@ -143,6 +144,32 @@ fn main() { ; let t = c.0; c.0 = a; a = t; + + let a = b; + let b = a; + + let mut c = 1; + let mut d = 2; + d = c; + c = d; + + let mut b = 1; + let a = b; + b = a; + + let b = 1; + let a = 2; + + let t = b; + let b = a; + let a = t; + + let mut b = 1; + let mut a = 2; + + let t = b; + b = a; + a = t; } fn issue_8154() { diff --git a/src/tools/clippy/tests/ui/swap.stderr b/src/tools/clippy/tests/ui/swap.stderr index ee4b7a508..f0acbfe25 100644 --- a/src/tools/clippy/tests/ui/swap.stderr +++ b/src/tools/clippy/tests/ui/swap.stderr @@ -1,5 +1,5 @@ error: this looks like you are swapping `bar.a` and `bar.b` manually - --> $DIR/swap.rs:24:5 + --> $DIR/swap.rs:25:5 | LL | / let temp = bar.a; LL | | bar.a = bar.b; @@ -10,7 +10,7 @@ LL | | bar.b = temp; = note: `-D clippy::manual-swap` implied by `-D warnings` error: this looks like you are swapping elements of `foo` manually - --> $DIR/swap.rs:36:5 + --> $DIR/swap.rs:37:5 | LL | / let temp = foo[0]; LL | | foo[0] = foo[1]; @@ -18,7 +18,7 @@ LL | | foo[1] = temp; | |_________________^ help: try: `foo.swap(0, 1)` error: this looks like you are swapping elements of `foo` manually - --> $DIR/swap.rs:45:5 + --> $DIR/swap.rs:46:5 | LL | / let temp = foo[0]; LL | | foo[0] = foo[1]; @@ -26,7 +26,7 @@ LL | | foo[1] = temp; | |_________________^ help: try: `foo.swap(0, 1)` error: this looks like you are swapping elements of `foo` manually - --> $DIR/swap.rs:64:5 + --> $DIR/swap.rs:65:5 | LL | / let temp = foo[0]; LL | | foo[0] = foo[1]; @@ -34,7 +34,7 @@ LL | | foo[1] = temp; | |_________________^ help: try: `foo.swap(0, 1)` error: this looks like you are swapping `a` and `b` manually - --> $DIR/swap.rs:75:5 + --> $DIR/swap.rs:76:5 | LL | / a ^= b; LL | | b ^= a; @@ -42,7 +42,7 @@ LL | | a ^= b; | |___________^ help: try: `std::mem::swap(&mut a, &mut b)` error: this looks like you are swapping `bar.a` and `bar.b` manually - --> $DIR/swap.rs:83:5 + --> $DIR/swap.rs:84:5 | LL | / bar.a ^= bar.b; LL | | bar.b ^= bar.a; @@ -50,7 +50,7 @@ LL | | bar.a ^= bar.b; | |___________________^ help: try: `std::mem::swap(&mut bar.a, &mut bar.b)` error: this looks like you are swapping elements of `foo` manually - --> $DIR/swap.rs:91:5 + --> $DIR/swap.rs:92:5 | LL | / foo[0] ^= foo[1]; LL | | foo[1] ^= foo[0]; @@ -58,7 +58,7 @@ LL | | foo[0] ^= foo[1]; | |_____________________^ help: try: `foo.swap(0, 1)` error: this looks like you are swapping `foo[0][1]` and `bar[1][0]` manually - --> $DIR/swap.rs:120:5 + --> $DIR/swap.rs:121:5 | LL | / let temp = foo[0][1]; LL | | foo[0][1] = bar[1][0]; @@ -68,7 +68,7 @@ LL | | bar[1][0] = temp; = note: or maybe you should use `std::mem::replace`? error: this looks like you are swapping `a` and `b` manually - --> $DIR/swap.rs:134:7 + --> $DIR/swap.rs:135:7 | LL | ; let t = a; | _______^ @@ -79,7 +79,7 @@ LL | | b = t; = note: or maybe you should use `std::mem::replace`? error: this looks like you are swapping `c.0` and `a` manually - --> $DIR/swap.rs:143:7 + --> $DIR/swap.rs:144:7 | LL | ; let t = c.0; | _______^ @@ -89,8 +89,18 @@ LL | | a = t; | = note: or maybe you should use `std::mem::replace`? +error: this looks like you are swapping `b` and `a` manually + --> $DIR/swap.rs:170:5 + | +LL | / let t = b; +LL | | b = a; +LL | | a = t; + | |_________^ help: try: `std::mem::swap(&mut b, &mut a)` + | + = note: or maybe you should use `std::mem::replace`? + error: this looks like you are trying to swap `a` and `b` - --> $DIR/swap.rs:131:5 + --> $DIR/swap.rs:132:5 | LL | / a = b; LL | | b = a; @@ -100,7 +110,7 @@ LL | | b = a; = note: `-D clippy::almost-swapped` implied by `-D warnings` error: this looks like you are trying to swap `c.0` and `a` - --> $DIR/swap.rs:140:5 + --> $DIR/swap.rs:141:5 | LL | / c.0 = a; LL | | a = c.0; @@ -108,8 +118,35 @@ LL | | a = c.0; | = note: or maybe you should use `std::mem::replace`? +error: this looks like you are trying to swap `a` and `b` + --> $DIR/swap.rs:148:5 + | +LL | / let a = b; +LL | | let b = a; + | |_____________^ help: try: `std::mem::swap(&mut a, &mut b)` + | + = note: or maybe you should use `std::mem::replace`? + +error: this looks like you are trying to swap `d` and `c` + --> $DIR/swap.rs:153:5 + | +LL | / d = c; +LL | | c = d; + | |_________^ help: try: `std::mem::swap(&mut d, &mut c)` + | + = note: or maybe you should use `std::mem::replace`? + +error: this looks like you are trying to swap `a` and `b` + --> $DIR/swap.rs:157:5 + | +LL | / let a = b; +LL | | b = a; + | |_________^ help: try: `std::mem::swap(&mut a, &mut b)` + | + = note: or maybe you should use `std::mem::replace`? + error: this looks like you are swapping `s.0.x` and `s.0.y` manually - --> $DIR/swap.rs:178:5 + --> $DIR/swap.rs:205:5 | LL | / let t = s.0.x; LL | | s.0.x = s.0.y; @@ -118,5 +155,5 @@ LL | | s.0.y = t; | = note: or maybe you should use `std::mem::replace`? -error: aborting due to 13 previous errors +error: aborting due to 17 previous errors diff --git a/src/tools/clippy/tests/ui/track-diagnostics.stderr b/src/tools/clippy/tests/ui/track-diagnostics.stderr index ec3031862..39418d359 100644 --- a/src/tools/clippy/tests/ui/track-diagnostics.stderr +++ b/src/tools/clippy/tests/ui/track-diagnostics.stderr @@ -2,7 +2,7 @@ error[E0308]: mismatched types --> $DIR/track-diagnostics.rs:LL:CC | LL | const S: A = B; - | ^ expected struct `A`, found struct `B` + | ^ expected `A`, found `B` -Ztrack-diagnostics: created at compiler/rustc_infer/src/infer/error_reporting/mod.rs:LL:CC error: aborting due to previous error diff --git a/src/tools/clippy/tests/ui/transmute_int_to_non_zero.rs b/src/tools/clippy/tests/ui/transmute_int_to_non_zero.rs new file mode 100644 index 000000000..a38406782 --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_int_to_non_zero.rs @@ -0,0 +1,41 @@ +#![warn(clippy::transmute_int_to_non_zero)] + +use core::num::*; + +fn main() { + let int_u8: u8 = 1; + let int_u16: u16 = 1; + let int_u32: u32 = 1; + let int_u64: u64 = 1; + let int_u128: u128 = 1; + + let int_i8: i8 = 1; + let int_i16: i16 = 1; + let int_i32: i32 = 1; + let int_i64: i64 = 1; + let int_i128: i128 = 1; + + let _: NonZeroU8 = unsafe { std::mem::transmute(int_u8) }; + let _: NonZeroU16 = unsafe { std::mem::transmute(int_u16) }; + let _: NonZeroU32 = unsafe { std::mem::transmute(int_u32) }; + let _: NonZeroU64 = unsafe { std::mem::transmute(int_u64) }; + let _: NonZeroU128 = unsafe { std::mem::transmute(int_u128) }; + + let _: NonZeroI8 = unsafe { std::mem::transmute(int_i8) }; + let _: NonZeroI16 = unsafe { std::mem::transmute(int_i16) }; + let _: NonZeroI32 = unsafe { std::mem::transmute(int_i32) }; + let _: NonZeroI64 = unsafe { std::mem::transmute(int_i64) }; + let _: NonZeroI128 = unsafe { std::mem::transmute(int_i128) }; + + let _: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(int_u8) }; + let _: NonZeroU16 = unsafe { NonZeroU16::new_unchecked(int_u16) }; + let _: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(int_u32) }; + let _: NonZeroU64 = unsafe { NonZeroU64::new_unchecked(int_u64) }; + let _: NonZeroU128 = unsafe { NonZeroU128::new_unchecked(int_u128) }; + + let _: NonZeroI8 = unsafe { NonZeroI8::new_unchecked(int_i8) }; + let _: NonZeroI16 = unsafe { NonZeroI16::new_unchecked(int_i16) }; + let _: NonZeroI32 = unsafe { NonZeroI32::new_unchecked(int_i32) }; + let _: NonZeroI64 = unsafe { NonZeroI64::new_unchecked(int_i64) }; + let _: NonZeroI128 = unsafe { NonZeroI128::new_unchecked(int_i128) }; +} diff --git a/src/tools/clippy/tests/ui/transmute_int_to_non_zero.stderr b/src/tools/clippy/tests/ui/transmute_int_to_non_zero.stderr new file mode 100644 index 000000000..33f8ce79e --- /dev/null +++ b/src/tools/clippy/tests/ui/transmute_int_to_non_zero.stderr @@ -0,0 +1,64 @@ +error: transmute from a `u8` to a `NonZeroU8` + --> $DIR/transmute_int_to_non_zero.rs:18:33 + | +LL | let _: NonZeroU8 = unsafe { std::mem::transmute(int_u8) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroU8::new_unchecked(int_u8)` + | + = note: `-D clippy::transmute-int-to-non-zero` implied by `-D warnings` + +error: transmute from a `u16` to a `NonZeroU16` + --> $DIR/transmute_int_to_non_zero.rs:19:34 + | +LL | let _: NonZeroU16 = unsafe { std::mem::transmute(int_u16) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroU16::new_unchecked(int_u16)` + +error: transmute from a `u32` to a `NonZeroU32` + --> $DIR/transmute_int_to_non_zero.rs:20:34 + | +LL | let _: NonZeroU32 = unsafe { std::mem::transmute(int_u32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroU32::new_unchecked(int_u32)` + +error: transmute from a `u64` to a `NonZeroU64` + --> $DIR/transmute_int_to_non_zero.rs:21:34 + | +LL | let _: NonZeroU64 = unsafe { std::mem::transmute(int_u64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroU64::new_unchecked(int_u64)` + +error: transmute from a `u128` to a `NonZeroU128` + --> $DIR/transmute_int_to_non_zero.rs:22:35 + | +LL | let _: NonZeroU128 = unsafe { std::mem::transmute(int_u128) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroU128::new_unchecked(int_u128)` + +error: transmute from a `i8` to a `NonZeroI8` + --> $DIR/transmute_int_to_non_zero.rs:24:33 + | +LL | let _: NonZeroI8 = unsafe { std::mem::transmute(int_i8) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroI8::new_unchecked(int_i8)` + +error: transmute from a `i16` to a `NonZeroI16` + --> $DIR/transmute_int_to_non_zero.rs:25:34 + | +LL | let _: NonZeroI16 = unsafe { std::mem::transmute(int_i16) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroI16::new_unchecked(int_i16)` + +error: transmute from a `i32` to a `NonZeroI32` + --> $DIR/transmute_int_to_non_zero.rs:26:34 + | +LL | let _: NonZeroI32 = unsafe { std::mem::transmute(int_i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroI32::new_unchecked(int_i32)` + +error: transmute from a `i64` to a `NonZeroI64` + --> $DIR/transmute_int_to_non_zero.rs:27:34 + | +LL | let _: NonZeroI64 = unsafe { std::mem::transmute(int_i64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroI64::new_unchecked(int_i64)` + +error: transmute from a `i128` to a `NonZeroI128` + --> $DIR/transmute_int_to_non_zero.rs:28:35 + | +LL | let _: NonZeroI128 = unsafe { std::mem::transmute(int_i128) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `NonZeroI128::new_unchecked(int_i128)` + +error: aborting due to 10 previous errors + diff --git a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed index 7263abac1..55307506e 100644 --- a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed +++ b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.fixed @@ -51,6 +51,8 @@ fn main() { // e is a function pointer type and U is an integer; fptr-addr-cast let _usize_from_fn_ptr_transmute = unsafe { foo as usize }; let _usize_from_fn_ptr = foo as *const usize; + + let _usize_from_ref = unsafe { &1u32 as *const u32 as usize }; } // If a ref-to-ptr cast of this form where the pointer type points to a type other diff --git a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs index d8e4421d4..e7360f3f9 100644 --- a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs +++ b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.rs @@ -51,6 +51,8 @@ fn main() { // e is a function pointer type and U is an integer; fptr-addr-cast let _usize_from_fn_ptr_transmute = unsafe { transmute::<fn(usize) -> u8, usize>(foo) }; let _usize_from_fn_ptr = foo as *const usize; + + let _usize_from_ref = unsafe { transmute::<*const u32, usize>(&1u32) }; } // If a ref-to-ptr cast of this form where the pointer type points to a type other diff --git a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr index de9418c8d..e862fcb67 100644 --- a/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr +++ b/src/tools/clippy/tests/ui/transmutes_expressible_as_ptr_casts.stderr @@ -46,11 +46,17 @@ error: transmute from `fn(usize) -> u8` to `usize` which could be expressed as a LL | let _usize_from_fn_ptr_transmute = unsafe { transmute::<fn(usize) -> u8, usize>(foo) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `foo as usize` +error: transmute from `*const u32` to `usize` which could be expressed as a pointer cast instead + --> $DIR/transmutes_expressible_as_ptr_casts.rs:55:36 + | +LL | let _usize_from_ref = unsafe { transmute::<*const u32, usize>(&1u32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&1u32 as *const u32 as usize` + error: transmute from a reference to a pointer - --> $DIR/transmutes_expressible_as_ptr_casts.rs:64:14 + --> $DIR/transmutes_expressible_as_ptr_casts.rs:66:14 | LL | unsafe { transmute::<&[i32; 1], *const u8>(in_param) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `in_param as *const [i32; 1] as *const u8` -error: aborting due to 8 previous errors +error: aborting due to 9 previous errors diff --git a/src/tools/clippy/tests/ui/type_repetition_in_bounds.rs b/src/tools/clippy/tests/ui/type_repetition_in_bounds.rs index 2eca1f470..8b4613b3f 100644 --- a/src/tools/clippy/tests/ui/type_repetition_in_bounds.rs +++ b/src/tools/clippy/tests/ui/type_repetition_in_bounds.rs @@ -1,4 +1,5 @@ #![deny(clippy::type_repetition_in_bounds)] +#![allow(clippy::extra_unused_type_parameters)] use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; diff --git a/src/tools/clippy/tests/ui/type_repetition_in_bounds.stderr b/src/tools/clippy/tests/ui/type_repetition_in_bounds.stderr index 70d700c1c..a90df03c0 100644 --- a/src/tools/clippy/tests/ui/type_repetition_in_bounds.stderr +++ b/src/tools/clippy/tests/ui/type_repetition_in_bounds.stderr @@ -1,5 +1,5 @@ error: this type has already been used as a bound predicate - --> $DIR/type_repetition_in_bounds.rs:8:5 + --> $DIR/type_repetition_in_bounds.rs:9:5 | LL | T: Clone, | ^^^^^^^^ @@ -12,7 +12,7 @@ LL | #![deny(clippy::type_repetition_in_bounds)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this type has already been used as a bound predicate - --> $DIR/type_repetition_in_bounds.rs:25:5 + --> $DIR/type_repetition_in_bounds.rs:26:5 | LL | Self: Copy + Default + Ord, | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -20,7 +20,7 @@ LL | Self: Copy + Default + Ord, = help: consider combining the bounds: `Self: Clone + Copy + Default + Ord` error: this type has already been used as a bound predicate - --> $DIR/type_repetition_in_bounds.rs:85:5 + --> $DIR/type_repetition_in_bounds.rs:86:5 | LL | T: Clone, | ^^^^^^^^ @@ -28,7 +28,7 @@ LL | T: Clone, = help: consider combining the bounds: `T: ?Sized + Clone` error: this type has already been used as a bound predicate - --> $DIR/type_repetition_in_bounds.rs:90:5 + --> $DIR/type_repetition_in_bounds.rs:91:5 | LL | T: ?Sized, | ^^^^^^^^^ diff --git a/src/tools/clippy/tests/ui/uninlined_format_args.fixed b/src/tools/clippy/tests/ui/uninlined_format_args.fixed index 9d08e80cf..cbd5cc5fc 100644 --- a/src/tools/clippy/tests/ui/uninlined_format_args.fixed +++ b/src/tools/clippy/tests/ui/uninlined_format_args.fixed @@ -174,3 +174,7 @@ fn _meets_msrv() { let local_i32 = 1; println!("expand='{local_i32}'"); } + +fn _do_not_fire() { + println!("{:?}", None::<()>); +} diff --git a/src/tools/clippy/tests/ui/uninlined_format_args.rs b/src/tools/clippy/tests/ui/uninlined_format_args.rs index 35b3677a8..cf0ea5be4 100644 --- a/src/tools/clippy/tests/ui/uninlined_format_args.rs +++ b/src/tools/clippy/tests/ui/uninlined_format_args.rs @@ -179,3 +179,7 @@ fn _meets_msrv() { let local_i32 = 1; println!("expand='{}'", local_i32); } + +fn _do_not_fire() { + println!("{:?}", None::<()>); +} diff --git a/src/tools/clippy/tests/ui/unnecessary_safety_comment.rs b/src/tools/clippy/tests/ui/unnecessary_safety_comment.rs index 7fefea705..89fedb145 100644 --- a/src/tools/clippy/tests/ui/unnecessary_safety_comment.rs +++ b/src/tools/clippy/tests/ui/unnecessary_safety_comment.rs @@ -48,4 +48,21 @@ fn unnecessary_on_stmt_and_expr() -> u32 { 24 } +mod issue_10084 { + unsafe fn bar() -> i32 { + 42 + } + + macro_rules! foo { + () => { + // SAFETY: This is necessary + unsafe { bar() } + }; + } + + fn main() { + foo!(); + } +} + fn main() {} diff --git a/src/tools/clippy/tests/ui/unreadable_literal.fixed b/src/tools/clippy/tests/ui/unreadable_literal.fixed index a67363b09..13e5feb19 100644 --- a/src/tools/clippy/tests/ui/unreadable_literal.fixed +++ b/src/tools/clippy/tests/ui/unreadable_literal.fixed @@ -23,7 +23,7 @@ fn main() { let _good = ( 0b1011_i64, 0o1_234_u32, - 0x0123_4567, + 0x1_234_567, 65536, 1_2345_6789, 1234_f32, diff --git a/src/tools/clippy/tests/ui/unreadable_literal.stderr b/src/tools/clippy/tests/ui/unreadable_literal.stderr index b51130c6a..450121b1c 100644 --- a/src/tools/clippy/tests/ui/unreadable_literal.stderr +++ b/src/tools/clippy/tests/ui/unreadable_literal.stderr @@ -1,11 +1,3 @@ -error: digits of hex or binary literal not grouped by four - --> $DIR/unreadable_literal.rs:26:9 - | -LL | 0x1_234_567, - | ^^^^^^^^^^^ help: consider: `0x0123_4567` - | - = note: `-D clippy::unusual-byte-groupings` implied by `-D warnings` - error: long literal lacking separators --> $DIR/unreadable_literal.rs:34:17 | @@ -68,5 +60,5 @@ error: long literal lacking separators LL | let _fail5 = 1.100300400; | ^^^^^^^^^^^ help: consider: `1.100_300_400` -error: aborting due to 11 previous errors +error: aborting due to 10 previous errors diff --git a/src/tools/clippy/tests/ui/unused_io_amount.rs b/src/tools/clippy/tests/ui/unused_io_amount.rs index 4b0595581..8d3e094b7 100644 --- a/src/tools/clippy/tests/ui/unused_io_amount.rs +++ b/src/tools/clippy/tests/ui/unused_io_amount.rs @@ -63,6 +63,14 @@ fn combine_or(file: &str) -> Result<(), Error> { Ok(()) } +fn is_ok_err<T: io::Read + io::Write>(s: &mut T) { + s.write(b"ok").is_ok(); + s.write(b"err").is_err(); + let mut buf = [0u8; 0]; + s.read(&mut buf).is_ok(); + s.read(&mut buf).is_err(); +} + async fn bad_async_write<W: AsyncWrite + Unpin>(w: &mut W) { w.write(b"hello world").await.unwrap(); } diff --git a/src/tools/clippy/tests/ui/unused_io_amount.stderr b/src/tools/clippy/tests/ui/unused_io_amount.stderr index 7ba7e09c0..0865c5213 100644 --- a/src/tools/clippy/tests/ui/unused_io_amount.stderr +++ b/src/tools/clippy/tests/ui/unused_io_amount.stderr @@ -82,13 +82,45 @@ LL | | .expect("error"); error: written amount is not handled --> $DIR/unused_io_amount.rs:67:5 | +LL | s.write(b"ok").is_ok(); + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `Write::write_all` instead, or handle partial writes + +error: written amount is not handled + --> $DIR/unused_io_amount.rs:68:5 + | +LL | s.write(b"err").is_err(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `Write::write_all` instead, or handle partial writes + +error: read amount is not handled + --> $DIR/unused_io_amount.rs:70:5 + | +LL | s.read(&mut buf).is_ok(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `Read::read_exact` instead, or handle partial reads + +error: read amount is not handled + --> $DIR/unused_io_amount.rs:71:5 + | +LL | s.read(&mut buf).is_err(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: use `Read::read_exact` instead, or handle partial reads + +error: written amount is not handled + --> $DIR/unused_io_amount.rs:75:5 + | LL | w.write(b"hello world").await.unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use `AsyncWriteExt::write_all` instead, or handle partial writes error: read amount is not handled - --> $DIR/unused_io_amount.rs:72:5 + --> $DIR/unused_io_amount.rs:80:5 | LL | r.read(&mut buf[..]).await.unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -96,7 +128,7 @@ LL | r.read(&mut buf[..]).await.unwrap(); = help: use `AsyncReadExt::read_exact` instead, or handle partial reads error: written amount is not handled - --> $DIR/unused_io_amount.rs:85:9 + --> $DIR/unused_io_amount.rs:93:9 | LL | w.write(b"hello world").await?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -104,7 +136,7 @@ LL | w.write(b"hello world").await?; = help: use `AsyncWriteExt::write_all` instead, or handle partial writes error: read amount is not handled - --> $DIR/unused_io_amount.rs:93:9 + --> $DIR/unused_io_amount.rs:101:9 | LL | r.read(&mut buf[..]).await.or(Err(Error::Kind))?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -112,7 +144,7 @@ LL | r.read(&mut buf[..]).await.or(Err(Error::Kind))?; = help: use `AsyncReadExt::read_exact` instead, or handle partial reads error: written amount is not handled - --> $DIR/unused_io_amount.rs:101:5 + --> $DIR/unused_io_amount.rs:109:5 | LL | w.write(b"hello world").await.unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -120,12 +152,12 @@ LL | w.write(b"hello world").await.unwrap(); = help: use `AsyncWriteExt::write_all` instead, or handle partial writes error: read amount is not handled - --> $DIR/unused_io_amount.rs:106:5 + --> $DIR/unused_io_amount.rs:114:5 | LL | r.read(&mut buf[..]).await.unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use `AsyncReadExt::read_exact` instead, or handle partial reads -error: aborting due to 16 previous errors +error: aborting due to 20 previous errors diff --git a/src/tools/clippy/tests/ui/wildcard_enum_match_arm.fixed b/src/tools/clippy/tests/ui/wildcard_enum_match_arm.fixed index 236074978..293bf75a7 100644 --- a/src/tools/clippy/tests/ui/wildcard_enum_match_arm.fixed +++ b/src/tools/clippy/tests/ui/wildcard_enum_match_arm.fixed @@ -96,7 +96,7 @@ fn main() { } match Enum::A { Enum::A => (), - Enum::B | _ => (), + Enum::B | Enum::__Private => (), } } } diff --git a/src/tools/clippy/tests/ui/wildcard_enum_match_arm.stderr b/src/tools/clippy/tests/ui/wildcard_enum_match_arm.stderr index efecc9576..30d29aa4e 100644 --- a/src/tools/clippy/tests/ui/wildcard_enum_match_arm.stderr +++ b/src/tools/clippy/tests/ui/wildcard_enum_match_arm.stderr @@ -34,11 +34,11 @@ error: wildcard matches known variants and will also match future added variants LL | _ => {}, | ^ help: try this: `ErrorKind::PermissionDenied | _` -error: wildcard matches known variants and will also match future added variants +error: wildcard match will also match any future added variants --> $DIR/wildcard_enum_match_arm.rs:99:13 | LL | _ => (), - | ^ help: try this: `Enum::B | _` + | ^ help: try this: `Enum::B | Enum::__Private` error: aborting due to 6 previous errors diff --git a/src/tools/compiletest/Cargo.toml b/src/tools/compiletest/Cargo.toml index 1911f0f9c..0db043a4f 100644 --- a/src/tools/compiletest/Cargo.toml +++ b/src/tools/compiletest/Cargo.toml @@ -9,13 +9,14 @@ diff = "0.1.10" unified-diff = "0.2.1" getopts = "0.2" miropt-test-tools = { path = "../miropt-test-tools" } +build_helper = { path = "../build_helper" } tracing = "0.1" tracing-subscriber = { version = "0.3.3", default-features = false, features = ["fmt", "env-filter", "smallvec", "parking_lot", "ansi"] } regex = "1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" rustfix = "0.6.0" -lazy_static = "1.0" +once_cell = "1.16.0" walkdir = "2" glob = "0.3.0" lazycell = "1.3.0" @@ -24,5 +25,5 @@ lazycell = "1.3.0" libc = "0.2" [target.'cfg(windows)'.dependencies] -miow = "0.3" +miow = "0.5" winapi = { version = "0.3", features = ["winerror"] } diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index a5f5eb447..7fe2e6257 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -123,6 +123,7 @@ pub enum FailMode { pub enum CompareMode { Polonius, Chalk, + NextSolver, SplitDwarf, SplitDwarfSingle, } @@ -132,6 +133,7 @@ impl CompareMode { match *self { CompareMode::Polonius => "polonius", CompareMode::Chalk => "chalk", + CompareMode::NextSolver => "next-solver", CompareMode::SplitDwarf => "split-dwarf", CompareMode::SplitDwarfSingle => "split-dwarf-single", } @@ -141,6 +143,7 @@ impl CompareMode { match s.as_str() { "polonius" => CompareMode::Polonius, "chalk" => CompareMode::Chalk, + "next-solver" => CompareMode::NextSolver, "split-dwarf" => CompareMode::SplitDwarf, "split-dwarf-single" => CompareMode::SplitDwarfSingle, x => panic!("unknown --compare-mode option: {}", x), @@ -377,6 +380,9 @@ pub struct Config { /// Whether to rerun tests even if the inputs are unchanged. pub force_rerun: bool, + /// Only rerun the tests that result has been modified accoring to Git status + pub only_modified: bool, + pub target_cfg: LazyCell<TargetCfg>, } diff --git a/src/tools/compiletest/src/errors.rs b/src/tools/compiletest/src/errors.rs index 054235ec1..c33e66e02 100644 --- a/src/tools/compiletest/src/errors.rs +++ b/src/tools/compiletest/src/errors.rs @@ -7,7 +7,7 @@ use std::io::BufReader; use std::path::Path; use std::str::FromStr; -use lazy_static::lazy_static; +use once_cell::sync::Lazy; use regex::Regex; use tracing::*; @@ -117,10 +117,8 @@ fn parse_expected( // //~^^^^^ // //[cfg1]~ // //[cfg1,cfg2]~^^ - lazy_static! { - static ref RE: Regex = - Regex::new(r"//(?:\[(?P<cfgs>[\w,]+)])?~(?P<adjust>\||\^*)").unwrap(); - } + static RE: Lazy<Regex> = + Lazy::new(|| Regex::new(r"//(?:\[(?P<cfgs>[\w,]+)])?~(?P<adjust>\||\^*)").unwrap()); let captures = RE.captures(line)?; diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index 423299495..d9b39927c 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -710,6 +710,7 @@ impl Config { match self.compare_mode { Some(CompareMode::Polonius) => name == "compare-mode-polonius", Some(CompareMode::Chalk) => name == "compare-mode-chalk", + Some(CompareMode::NextSolver) => name == "compare-mode-next-solver", Some(CompareMode::SplitDwarf) => name == "compare-mode-split-dwarf", Some(CompareMode::SplitDwarfSingle) => name == "compare-mode-split-dwarf-single", None => false, @@ -925,7 +926,7 @@ pub fn make_test_description<R: Read>( cfg: Option<&str>, ) -> test::TestDesc { let mut ignore = false; - let ignore_message = None; + let mut ignore_message = None; let mut should_fail = false; let rustc_has_profiler_support = env::var_os("RUSTC_PROFILER_SUPPORT").is_some(); @@ -934,12 +935,14 @@ pub fn make_test_description<R: Read>( let has_asan = util::ASAN_SUPPORTED_TARGETS.contains(&&*config.target); let has_cfi = util::CFI_SUPPORTED_TARGETS.contains(&&*config.target); let has_kcfi = util::KCFI_SUPPORTED_TARGETS.contains(&&*config.target); + let has_kasan = util::KASAN_SUPPORTED_TARGETS.contains(&&*config.target); let has_lsan = util::LSAN_SUPPORTED_TARGETS.contains(&&*config.target); let has_msan = util::MSAN_SUPPORTED_TARGETS.contains(&&*config.target); let has_tsan = util::TSAN_SUPPORTED_TARGETS.contains(&&*config.target); let has_hwasan = util::HWASAN_SUPPORTED_TARGETS.contains(&&*config.target); let has_memtag = util::MEMTAG_SUPPORTED_TARGETS.contains(&&*config.target); let has_shadow_call_stack = util::SHADOWCALLSTACK_SUPPORTED_TARGETS.contains(&&*config.target); + let has_xray = util::XRAY_SUPPORTED_TARGETS.contains(&&*config.target); // For tests using the `needs-rust-lld` directive (e.g. for `-Zgcc-ld=lld`), we need to find // whether `rust-lld` is present in the compiler under test. @@ -965,41 +968,69 @@ pub fn make_test_description<R: Read>( if revision.is_some() && revision != cfg { return; } + macro_rules! reason { + ($e:expr) => { + ignore |= match $e { + true => { + ignore_message = Some(stringify!($e)); + true + } + false => ignore, + } + }; + } ignore = match config.parse_cfg_name_directive(ln, "ignore") { - ParsedNameDirective::Match => true, + ParsedNameDirective::Match => { + ignore_message = Some("cfg -> ignore => Match"); + true + } ParsedNameDirective::NoMatch => ignore, }; + if config.has_cfg_prefix(ln, "only") { ignore = match config.parse_cfg_name_directive(ln, "only") { ParsedNameDirective::Match => ignore, - ParsedNameDirective::NoMatch => true, + ParsedNameDirective::NoMatch => { + ignore_message = Some("cfg -> only => NoMatch"); + true + } }; } - ignore |= ignore_llvm(config, ln); - ignore |= - config.run_clang_based_tests_with.is_none() && config.parse_needs_matching_clang(ln); - ignore |= !has_asm_support && config.parse_name_directive(ln, "needs-asm-support"); - ignore |= !rustc_has_profiler_support && config.parse_needs_profiler_support(ln); - ignore |= !config.run_enabled() && config.parse_name_directive(ln, "needs-run-enabled"); - ignore |= !rustc_has_sanitizer_support - && config.parse_name_directive(ln, "needs-sanitizer-support"); - ignore |= !has_asan && config.parse_name_directive(ln, "needs-sanitizer-address"); - ignore |= !has_cfi && config.parse_name_directive(ln, "needs-sanitizer-cfi"); - ignore |= !has_kcfi && config.parse_name_directive(ln, "needs-sanitizer-kcfi"); - ignore |= !has_lsan && config.parse_name_directive(ln, "needs-sanitizer-leak"); - ignore |= !has_msan && config.parse_name_directive(ln, "needs-sanitizer-memory"); - ignore |= !has_tsan && config.parse_name_directive(ln, "needs-sanitizer-thread"); - ignore |= !has_hwasan && config.parse_name_directive(ln, "needs-sanitizer-hwaddress"); - ignore |= !has_memtag && config.parse_name_directive(ln, "needs-sanitizer-memtag"); - ignore |= !has_shadow_call_stack - && config.parse_name_directive(ln, "needs-sanitizer-shadow-call-stack"); - ignore |= !config.can_unwind() && config.parse_name_directive(ln, "needs-unwind"); - ignore |= config.target == "wasm32-unknown-unknown" - && config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS); - ignore |= config.debugger == Some(Debugger::Cdb) && ignore_cdb(config, ln); - ignore |= config.debugger == Some(Debugger::Gdb) && ignore_gdb(config, ln); - ignore |= config.debugger == Some(Debugger::Lldb) && ignore_lldb(config, ln); - ignore |= !has_rust_lld && config.parse_name_directive(ln, "needs-rust-lld"); + + reason!(ignore_llvm(config, ln)); + reason!( + config.run_clang_based_tests_with.is_none() && config.parse_needs_matching_clang(ln) + ); + reason!(!has_asm_support && config.parse_name_directive(ln, "needs-asm-support")); + reason!(!rustc_has_profiler_support && config.parse_needs_profiler_support(ln)); + reason!(!config.run_enabled() && config.parse_name_directive(ln, "needs-run-enabled")); + reason!( + !rustc_has_sanitizer_support + && config.parse_name_directive(ln, "needs-sanitizer-support") + ); + reason!(!has_asan && config.parse_name_directive(ln, "needs-sanitizer-address")); + reason!(!has_cfi && config.parse_name_directive(ln, "needs-sanitizer-cfi")); + reason!(!has_kcfi && config.parse_name_directive(ln, "needs-sanitizer-kcfi")); + reason!(!has_kasan && config.parse_name_directive(ln, "needs-sanitizer-kasan")); + reason!(!has_lsan && config.parse_name_directive(ln, "needs-sanitizer-leak")); + reason!(!has_msan && config.parse_name_directive(ln, "needs-sanitizer-memory")); + reason!(!has_tsan && config.parse_name_directive(ln, "needs-sanitizer-thread")); + reason!(!has_hwasan && config.parse_name_directive(ln, "needs-sanitizer-hwaddress")); + reason!(!has_memtag && config.parse_name_directive(ln, "needs-sanitizer-memtag")); + reason!( + !has_shadow_call_stack + && config.parse_name_directive(ln, "needs-sanitizer-shadow-call-stack") + ); + reason!(!config.can_unwind() && config.parse_name_directive(ln, "needs-unwind")); + reason!(!has_xray && config.parse_name_directive(ln, "needs-xray")); + reason!( + config.target == "wasm32-unknown-unknown" + && config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS) + ); + reason!(config.debugger == Some(Debugger::Cdb) && ignore_cdb(config, ln)); + reason!(config.debugger == Some(Debugger::Gdb) && ignore_gdb(config, ln)); + reason!(config.debugger == Some(Debugger::Lldb) && ignore_lldb(config, ln)); + reason!(!has_rust_lld && config.parse_name_directive(ln, "needs-rust-lld")); should_fail |= config.parse_name_directive(ln, "should-fail"); }); diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs index 3092c656c..1760c29ec 100644 --- a/src/tools/compiletest/src/main.rs +++ b/src/tools/compiletest/src/main.rs @@ -8,15 +8,17 @@ extern crate test; use crate::common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS}; use crate::common::{CompareMode, Config, Debugger, Mode, PassMode, TestPaths}; use crate::util::logv; +use build_helper::git::{get_git_modified_files, get_git_untracked_files}; +use core::panic; use getopts::Options; use lazycell::LazyCell; -use std::env; use std::ffi::OsString; use std::fs; use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::time::SystemTime; +use std::{env, vec}; use test::ColorConfig; use tracing::*; use walkdir::WalkDir; @@ -145,9 +147,10 @@ pub fn parse_config(args: Vec<String>) -> Config { "", "rustfix-coverage", "enable this to generate a Rustfix coverage file, which is saved in \ - `./<build_base>/rustfix_missing_coverage.txt`", + `./<build_base>/rustfix_missing_coverage.txt`", ) .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged") + .optflag("", "only-modified", "only run tests that result been modified") .optflag("h", "help", "show this message") .reqopt("", "channel", "current Rust channel", "CHANNEL") .optopt("", "edition", "default Rust edition", "EDITION"); @@ -279,6 +282,7 @@ pub fn parse_config(args: Vec<String>) -> Config { lldb_python_dir: matches.opt_str("lldb-python-dir"), verbose: matches.opt_present("verbose"), quiet: matches.opt_present("quiet"), + only_modified: matches.opt_present("only-modified"), color, remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from), compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse), @@ -521,8 +525,18 @@ pub fn test_opts(config: &Config) -> test::TestOpts { pub fn make_tests(config: &Config, tests: &mut Vec<test::TestDescAndFn>) { debug!("making tests from {:?}", config.src_base.display()); let inputs = common_inputs_stamp(config); - collect_tests_from_dir(config, &config.src_base, &PathBuf::new(), &inputs, tests) - .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display())); + let modified_tests = modified_tests(config, &config.src_base).unwrap_or_else(|err| { + panic!("modified_tests got error from dir: {}, error: {}", config.src_base.display(), err) + }); + collect_tests_from_dir( + config, + &config.src_base, + &PathBuf::new(), + &inputs, + tests, + &modified_tests, + ) + .unwrap_or_else(|_| panic!("Could not read tests from {}", config.src_base.display())); } /// Returns a stamp constructed from input files common to all test cases. @@ -561,12 +575,36 @@ fn common_inputs_stamp(config: &Config) -> Stamp { stamp } +fn modified_tests(config: &Config, dir: &Path) -> Result<Vec<PathBuf>, String> { + if !config.only_modified { + return Ok(vec![]); + } + let files = + get_git_modified_files(Some(dir), &vec!["rs", "stderr", "fixed"])?.unwrap_or(vec![]); + // Add new test cases to the list, it will be convenient in daily development. + let untracked_files = get_git_untracked_files(None)?.unwrap_or(vec![]); + + let all_paths = [&files[..], &untracked_files[..]].concat(); + let full_paths = { + let mut full_paths: Vec<PathBuf> = all_paths + .into_iter() + .map(|f| PathBuf::from(f).with_extension("").with_extension("rs")) + .filter_map(|f| if Path::new(&f).exists() { f.canonicalize().ok() } else { None }) + .collect(); + full_paths.dedup(); + full_paths.sort_unstable(); + full_paths + }; + Ok(full_paths) +} + fn collect_tests_from_dir( config: &Config, dir: &Path, relative_dir_path: &Path, inputs: &Stamp, tests: &mut Vec<test::TestDescAndFn>, + modified_tests: &Vec<PathBuf>, ) -> io::Result<()> { // Ignore directories that contain a file named `compiletest-ignore-dir`. if dir.join("compiletest-ignore-dir").exists() { @@ -597,7 +635,7 @@ fn collect_tests_from_dir( let file = file?; let file_path = file.path(); let file_name = file.file_name(); - if is_test(&file_name) { + if is_test(&file_name) && (!config.only_modified || modified_tests.contains(&file_path)) { debug!("found test file: {:?}", file_path.display()); let paths = TestPaths { file: file_path, relative_dir: relative_dir_path.to_path_buf() }; @@ -607,7 +645,14 @@ fn collect_tests_from_dir( let relative_file_path = relative_dir_path.join(file.file_name()); if &file_name != "auxiliary" { debug!("found directory: {:?}", file_path.display()); - collect_tests_from_dir(config, &file_path, &relative_file_path, inputs, tests)?; + collect_tests_from_dir( + config, + &file_path, + &relative_file_path, + inputs, + tests, + modified_tests, + )?; } } else { debug!("found other file/directory: {:?}", file_path.display()); diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 859c0f1da..41c23ff86 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -32,7 +32,7 @@ use std::process::{Child, Command, ExitStatus, Output, Stdio}; use std::str; use glob::glob; -use lazy_static::lazy_static; +use once_cell::sync::Lazy; use tracing::*; use crate::extract_gdb_version; @@ -52,9 +52,8 @@ fn disable_error_reporting<F: FnOnce() -> R, R>(f: F) -> R { use winapi::um::errhandlingapi::SetErrorMode; use winapi::um::winbase::SEM_NOGPFAULTERRORBOX; - lazy_static! { - static ref LOCK: Mutex<()> = Mutex::new(()); - } + static LOCK: Mutex<()> = Mutex::new(()); + // Error mode is a global variable, so lock it so only one thread will change it let _lock = LOCK.lock().unwrap(); @@ -1433,12 +1432,13 @@ impl<'test> TestCx<'test> { expect_help: bool, expect_note: bool, ) -> bool { - match actual_error.kind { - Some(ErrorKind::Help) => expect_help, - Some(ErrorKind::Note) => expect_note, - Some(ErrorKind::Error) | Some(ErrorKind::Warning) => true, - Some(ErrorKind::Suggestion) | None => false, - } + !actual_error.msg.is_empty() + && match actual_error.kind { + Some(ErrorKind::Help) => expect_help, + Some(ErrorKind::Note) => expect_note, + Some(ErrorKind::Error) | Some(ErrorKind::Warning) => true, + Some(ErrorKind::Suggestion) | None => false, + } } fn should_emit_metadata(&self, pm: Option<PassMode>) -> Emit { @@ -2030,6 +2030,9 @@ impl<'test> TestCx<'test> { Some(CompareMode::Chalk) => { rustc.args(&["-Ztrait-solver=chalk"]); } + Some(CompareMode::NextSolver) => { + rustc.args(&["-Ztrait-solver=next"]); + } Some(CompareMode::SplitDwarf) if self.config.target.contains("windows") => { rustc.args(&["-Csplit-debuginfo=unpacked", "-Zunstable-options"]); } @@ -2845,11 +2848,10 @@ impl<'test> TestCx<'test> { // the form <crate-name1>.<crate-disambiguator1>-in-<crate-name2>.<crate-disambiguator2>, // remove all crate-disambiguators. fn remove_crate_disambiguator_from_cgu(cgu: &str) -> String { - lazy_static! { - static ref RE: Regex = - Regex::new(r"^[^\.]+(?P<d1>\.[[:alnum:]]+)(-in-[^\.]+(?P<d2>\.[[:alnum:]]+))?") - .unwrap(); - } + static RE: Lazy<Regex> = Lazy::new(|| { + Regex::new(r"^[^\.]+(?P<d1>\.[[:alnum:]]+)(-in-[^\.]+(?P<d2>\.[[:alnum:]]+))?") + .unwrap() + }); let captures = RE.captures(cgu).unwrap_or_else(|| panic!("invalid cgu name encountered: {}", cgu)); @@ -3167,12 +3169,12 @@ impl<'test> TestCx<'test> { // 'uploaded "$TEST_BUILD_DIR/<test_executable>, waiting for result"' // is printed to stdout by the client and then captured in the ProcRes, // so it needs to be removed when comparing the run-pass test execution output - lazy_static! { - static ref REMOTE_TEST_RE: Regex = Regex::new( + static REMOTE_TEST_RE: Lazy<Regex> = Lazy::new(|| { + Regex::new( "^uploaded \"\\$TEST_BUILD_DIR(/[[:alnum:]_\\-.]+)+\", waiting for result\n" ) - .unwrap(); - } + .unwrap() + }); REMOTE_TEST_RE .replace( &self.normalize_output(&proc_res.stdout, &self.props.normalize_stdout), @@ -3617,10 +3619,8 @@ impl<'test> TestCx<'test> { // with placeholders as we do not want tests needing updated when compiler source code // changes. // eg. $SRC_DIR/libcore/mem.rs:323:14 becomes $SRC_DIR/libcore/mem.rs:LL:COL - lazy_static! { - static ref SRC_DIR_RE: Regex = - Regex::new("SRC_DIR(.+):\\d+:\\d+(: \\d+:\\d+)?").unwrap(); - } + static SRC_DIR_RE: Lazy<Regex> = + Lazy::new(|| Regex::new("SRC_DIR(.+):\\d+:\\d+(: \\d+:\\d+)?").unwrap()); normalized = SRC_DIR_RE.replace_all(&normalized, "SRC_DIR$1:LL:COL").into_owned(); @@ -3631,19 +3631,17 @@ impl<'test> TestCx<'test> { // since they duplicate actual errors and make the output hard to read. // This mirrors the regex in src/tools/tidy/src/style.rs, please update // both if either are changed. - lazy_static! { - static ref ANNOTATION_RE: Regex = Regex::new("\\s*//(\\[.*\\])?~.*").unwrap(); - } + static ANNOTATION_RE: Lazy<Regex> = + Lazy::new(|| Regex::new("\\s*//(\\[.*\\])?~.*").unwrap()); normalized = ANNOTATION_RE.replace_all(&normalized, "").into_owned(); // This code normalizes various hashes in v0 symbol mangling that is // emitted in the ui and mir-opt tests. - lazy_static! { - static ref V0_CRATE_HASH_PREFIX_RE: Regex = - Regex::new(r"_R.*?Cs[0-9a-zA-Z]+_").unwrap(); - static ref V0_CRATE_HASH_RE: Regex = Regex::new(r"Cs[0-9a-zA-Z]+_").unwrap(); - } + static V0_CRATE_HASH_PREFIX_RE: Lazy<Regex> = + Lazy::new(|| Regex::new(r"_R.*?Cs[0-9a-zA-Z]+_").unwrap()); + static V0_CRATE_HASH_RE: Lazy<Regex> = + Lazy::new(|| Regex::new(r"Cs[0-9a-zA-Z]+_").unwrap()); const V0_CRATE_HASH_PLACEHOLDER: &str = r"CsCRATE_HASH_"; if V0_CRATE_HASH_PREFIX_RE.is_match(&normalized) { @@ -3652,10 +3650,9 @@ impl<'test> TestCx<'test> { V0_CRATE_HASH_RE.replace_all(&normalized, V0_CRATE_HASH_PLACEHOLDER).into_owned(); } - lazy_static! { - static ref V0_BACK_REF_PREFIX_RE: Regex = Regex::new(r"\(_R.*?B[0-9a-zA-Z]_").unwrap(); - static ref V0_BACK_REF_RE: Regex = Regex::new(r"B[0-9a-zA-Z]_").unwrap(); - } + static V0_BACK_REF_PREFIX_RE: Lazy<Regex> = + Lazy::new(|| Regex::new(r"\(_R.*?B[0-9a-zA-Z]_").unwrap()); + static V0_BACK_REF_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"B[0-9a-zA-Z]_").unwrap()); const V0_BACK_REF_PLACEHOLDER: &str = r"B<REF>_"; if V0_BACK_REF_PREFIX_RE.is_match(&normalized) { @@ -3678,21 +3675,23 @@ impl<'test> TestCx<'test> { /// Replaces backslashes in paths with forward slashes, and replaces CRLF line endings /// with LF. fn normalize_platform_differences(output: &str) -> String { - lazy_static! { - /// Used to find Windows paths. - /// - /// It's not possible to detect paths in the error messages generally, but this is a - /// decent enough heuristic. - static ref PATH_BACKSLASH_RE: Regex = Regex::new(r#"(?x) + /// Used to find Windows paths. + /// + /// It's not possible to detect paths in the error messages generally, but this is a + /// decent enough heuristic. + static PATH_BACKSLASH_RE: Lazy<Regex> = Lazy::new(|| { + Regex::new( + r#"(?x) (?: # Match paths that don't include spaces. (?:\\[\pL\pN\.\-_']+)+\.\pL+ | # If the path starts with a well-known root, then allow spaces. \$(?:DIR|SRC_DIR|TEST_BUILD_DIR|BUILD_DIR|LIB_DIR)(?:\\[\pL\pN\.\-_' ]+)+ - )"# - ).unwrap(); - } + )"#, + ) + .unwrap() + }); let output = output.replace(r"\\", r"\"); diff --git a/src/tools/compiletest/src/util.rs b/src/tools/compiletest/src/util.rs index 6c63b760f..5f6a27e53 100644 --- a/src/tools/compiletest/src/util.rs +++ b/src/tools/compiletest/src/util.rs @@ -11,6 +11,8 @@ mod tests; pub const ASAN_SUPPORTED_TARGETS: &[&str] = &[ "aarch64-apple-darwin", + "aarch64-apple-ios", + "aarch64-apple-ios-sim", "aarch64-unknown-fuchsia", "aarch64-linux-android", "aarch64-unknown-linux-gnu", @@ -19,10 +21,12 @@ pub const ASAN_SUPPORTED_TARGETS: &[&str] = &[ "i686-linux-android", "i686-unknown-linux-gnu", "x86_64-apple-darwin", + "x86_64-apple-ios", "x86_64-unknown-fuchsia", "x86_64-linux-android", "x86_64-unknown-freebsd", "x86_64-unknown-linux-gnu", + "s390x-unknown-linux-gnu", ]; // FIXME(rcvalle): More targets are likely supported. @@ -44,23 +48,39 @@ pub const CFI_SUPPORTED_TARGETS: &[&str] = &[ pub const KCFI_SUPPORTED_TARGETS: &[&str] = &["aarch64-linux-none", "x86_64-linux-none"]; +pub const KASAN_SUPPORTED_TARGETS: &[&str] = &[ + "aarch64-unknown-none", + "riscv64gc-unknown-none-elf", + "riscv64imac-unknown-none-elf", + "x86_64-unknown-none", +]; + pub const LSAN_SUPPORTED_TARGETS: &[&str] = &[ // FIXME: currently broken, see #88132 // "aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", + "s390x-unknown-linux-gnu", ]; -pub const MSAN_SUPPORTED_TARGETS: &[&str] = - &["aarch64-unknown-linux-gnu", "x86_64-unknown-freebsd", "x86_64-unknown-linux-gnu"]; +pub const MSAN_SUPPORTED_TARGETS: &[&str] = &[ + "aarch64-unknown-linux-gnu", + "x86_64-unknown-freebsd", + "x86_64-unknown-linux-gnu", + "s390x-unknown-linux-gnu", +]; pub const TSAN_SUPPORTED_TARGETS: &[&str] = &[ "aarch64-apple-darwin", + "aarch64-apple-ios", + "aarch64-apple-ios-sim", "aarch64-unknown-linux-gnu", "x86_64-apple-darwin", + "x86_64-apple-ios", "x86_64-unknown-freebsd", "x86_64-unknown-linux-gnu", + "s390x-unknown-linux-gnu", ]; pub const HWASAN_SUPPORTED_TARGETS: &[&str] = @@ -71,6 +91,19 @@ pub const MEMTAG_SUPPORTED_TARGETS: &[&str] = pub const SHADOWCALLSTACK_SUPPORTED_TARGETS: &[&str] = &["aarch64-linux-android"]; +pub const XRAY_SUPPORTED_TARGETS: &[&str] = &[ + "aarch64-linux-android", + "aarch64-unknown-linux-gnu", + "aarch64-unknown-linux-musl", + "x86_64-linux-android", + "x86_64-unknown-freebsd", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", + "x86_64-unknown-netbsd", + "x86_64-unknown-none-linuxkernel", + "x86_64-unknown-openbsd", +]; + pub fn make_new_path(path: &str) -> String { assert!(cfg!(windows)); // Windows just uses PATH as the library search path, so we have to diff --git a/src/tools/error_index_generator/Cargo.toml b/src/tools/error_index_generator/Cargo.toml index f4dac6e94..76c2e330b 100644 --- a/src/tools/error_index_generator/Cargo.toml +++ b/src/tools/error_index_generator/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] mdbook = { version = "0.4", default-features = false, features = ["search"] } +rustc_error_codes = { version = "0.0.0", path = "../../../compiler/rustc_error_codes" } [[bin]] name = "error_index_generator" diff --git a/src/tools/error_index_generator/main.rs b/src/tools/error_index_generator/main.rs index 98eda97e2..f984275b1 100644 --- a/src/tools/error_index_generator/main.rs +++ b/src/tools/error_index_generator/main.rs @@ -2,9 +2,6 @@ extern crate rustc_driver; -// We use the function we generate from `register_diagnostics!`. -use crate::error_codes::error_codes; - use std::env; use std::error::Error; use std::fs::{self, File}; @@ -17,22 +14,6 @@ use std::str::FromStr; use mdbook::book::{parse_summary, BookItem, Chapter}; use mdbook::{Config, MDBook}; -macro_rules! register_diagnostics { - ($($error_code:ident: $message:expr,)+ ; $($undocumented:ident,)* ) => { - pub fn error_codes() -> Vec<(&'static str, Option<&'static str>)> { - let mut errors: Vec<(&str, Option<&str>)> = vec![ - $((stringify!($error_code), Some($message)),)+ - $((stringify!($undocumented), None),)+ - ]; - errors.sort(); - errors - } - } -} - -#[path = "../../../compiler/rustc_error_codes/src/error_codes.rs"] -mod error_codes; - enum OutputFormat { HTML, Markdown, @@ -55,11 +36,8 @@ fn render_markdown(output_path: &Path) -> Result<(), Box<dyn Error>> { write!(output_file, "# Rust Compiler Error Index\n")?; - for (err_code, description) in error_codes().iter() { - match description { - Some(ref desc) => write!(output_file, "## {}\n{}\n", err_code, desc)?, - None => {} - } + for (err_code, description) in rustc_error_codes::DIAGNOSTICS.iter() { + write!(output_file, "## {}\n{}\n", err_code, description)? } Ok(()) @@ -105,27 +83,23 @@ This page lists all the error codes emitted by the Rust compiler. " ); - let err_codes = error_codes(); + let err_codes = rustc_error_codes::DIAGNOSTICS; let mut chapters = Vec::with_capacity(err_codes.len()); for (err_code, explanation) in err_codes.iter() { - if let Some(explanation) = explanation { - introduction.push_str(&format!(" * [{0}](./{0}.html)\n", err_code)); - - let content = add_rust_attribute_on_codeblock(explanation); - chapters.push(BookItem::Chapter(Chapter { - name: err_code.to_string(), - content: format!("# Error code {}\n\n{}\n", err_code, content), - number: None, - sub_items: Vec::new(), - // We generate it into the `error_codes` folder. - path: Some(PathBuf::from(&format!("{}.html", err_code))), - source_path: None, - parent_names: Vec::new(), - })); - } else { - introduction.push_str(&format!(" * {}\n", err_code)); - } + introduction.push_str(&format!(" * [{0}](./{0}.html)\n", err_code)); + + let content = add_rust_attribute_on_codeblock(explanation); + chapters.push(BookItem::Chapter(Chapter { + name: err_code.to_string(), + content: format!("# Error code {}\n\n{}\n", err_code, content), + number: None, + sub_items: Vec::new(), + // We generate it into the `error_codes` folder. + path: Some(PathBuf::from(&format!("{}.html", err_code))), + source_path: None, + parent_names: Vec::new(), + })); } let mut config = Config::from_str(include_str!("book_config.toml"))?; diff --git a/src/tools/expand-yaml-anchors/src/main.rs b/src/tools/expand-yaml-anchors/src/main.rs index 8992d165d..3fc72ecbb 100644 --- a/src/tools/expand-yaml-anchors/src/main.rs +++ b/src/tools/expand-yaml-anchors/src/main.rs @@ -51,7 +51,7 @@ impl App { ["generate", ref base] => (Mode::Generate, PathBuf::from(base)), ["check", ref base] => (Mode::Check, PathBuf::from(base)), _ => { - eprintln!("usage: expand-yaml-anchors <source-dir> <dest-dir>"); + eprintln!("usage: expand-yaml-anchors <generate|check> <base-dir>"); std::process::exit(1); } }; diff --git a/src/tools/jsondoclint/src/validator.rs b/src/tools/jsondoclint/src/validator.rs index c6f55410e..a1f675a3b 100644 --- a/src/tools/jsondoclint/src/validator.rs +++ b/src/tools/jsondoclint/src/validator.rs @@ -71,6 +71,19 @@ impl<'a> Validator<'a> { } } + fn check_items(&mut self, id: &Id, items: &[Id]) { + let mut visited_ids = HashSet::with_capacity(items.len()); + + for item in items { + if !visited_ids.insert(item) { + self.fail( + id, + ErrorKind::Custom(format!("Duplicated entry in `items` field: `{item:?}`")), + ); + } + } + } + fn check_item(&mut self, id: &'a Id) { if let Some(item) = &self.krate.index.get(id) { item.links.values().for_each(|id| self.add_any_id(id)); @@ -83,9 +96,9 @@ impl<'a> Validator<'a> { ItemEnum::Enum(x) => self.check_enum(x), ItemEnum::Variant(x) => self.check_variant(x, id), ItemEnum::Function(x) => self.check_function(x), - ItemEnum::Trait(x) => self.check_trait(x), + ItemEnum::Trait(x) => self.check_trait(x, id), ItemEnum::TraitAlias(x) => self.check_trait_alias(x), - ItemEnum::Impl(x) => self.check_impl(x), + ItemEnum::Impl(x) => self.check_impl(x, id), ItemEnum::Typedef(x) => self.check_typedef(x), ItemEnum::OpaqueTy(x) => self.check_opaque_ty(x), ItemEnum::Constant(x) => self.check_constant(x), @@ -94,7 +107,7 @@ impl<'a> Validator<'a> { ItemEnum::Macro(x) => self.check_macro(x), ItemEnum::ProcMacro(x) => self.check_proc_macro(x), ItemEnum::Primitive(x) => self.check_primitive_type(x), - ItemEnum::Module(x) => self.check_module(x), + ItemEnum::Module(x) => self.check_module(x, id), // FIXME: Why don't these have their own structs? ItemEnum::ExternCrate { .. } => {} ItemEnum::AssocConst { type_, default: _ } => self.check_type(type_), @@ -112,7 +125,8 @@ impl<'a> Validator<'a> { } // Core checkers - fn check_module(&mut self, module: &'a Module) { + fn check_module(&mut self, module: &'a Module, id: &Id) { + self.check_items(id, &module.items); module.items.iter().for_each(|i| self.add_mod_item_id(i)); } @@ -181,7 +195,8 @@ impl<'a> Validator<'a> { self.check_fn_decl(&x.decl); } - fn check_trait(&mut self, x: &'a Trait) { + fn check_trait(&mut self, x: &'a Trait, id: &Id) { + self.check_items(id, &x.items); self.check_generics(&x.generics); x.items.iter().for_each(|i| self.add_trait_item_id(i)); x.bounds.iter().for_each(|i| self.check_generic_bound(i)); @@ -193,7 +208,8 @@ impl<'a> Validator<'a> { x.params.iter().for_each(|i| self.check_generic_bound(i)); } - fn check_impl(&mut self, x: &'a Impl) { + fn check_impl(&mut self, x: &'a Impl, id: &Id) { + self.check_items(id, &x.items); self.check_generics(&x.generics); if let Some(path) = &x.trait_ { self.check_path(path, PathKind::Trait); diff --git a/src/tools/publish_toolstate.py b/src/tools/publish_toolstate.py index 9c16ef2cb..395bcc745 100755 --- a/src/tools/publish_toolstate.py +++ b/src/tools/publish_toolstate.py @@ -68,52 +68,6 @@ def load_json_from_response(resp): print("Refusing to decode " + str(type(content)) + " to str") return json.loads(content_str) -def validate_maintainers(repo, github_token): - # type: (str, str) -> None - '''Ensure all maintainers are assignable on a GitHub repo''' - next_link_re = re.compile(r'<([^>]+)>; rel="next"') - - # Load the list of assignable people in the GitHub repo - assignable = [] # type: typing.List[str] - url = 'https://api.github.com/repos/' \ - + '%s/collaborators?per_page=100' % repo # type: typing.Optional[str] - while url is not None: - response = urllib2.urlopen(urllib2.Request(url, headers={ - 'Authorization': 'token ' + github_token, - # Properly load nested teams. - 'Accept': 'application/vnd.github.hellcat-preview+json', - })) - assignable.extend(user['login'] for user in load_json_from_response(response)) - # Load the next page if available - url = None - link_header = response.headers.get('Link') - if link_header: - matches = next_link_re.match(link_header) - if matches is not None: - url = matches.group(1) - - errors = False - for tool, maintainers in MAINTAINERS.items(): - for maintainer in maintainers: - if maintainer not in assignable: - errors = True - print( - "error: %s maintainer @%s is not assignable in the %s repo" - % (tool, maintainer, repo), - ) - - if errors: - print() - print(" To be assignable, a person needs to be explicitly listed as a") - print(" collaborator in the repository settings. The simple way to") - print(" fix this is to ask someone with 'admin' privileges on the repo") - print(" to add the person or whole team as a collaborator with 'read'") - print(" privileges. Those privileges don't grant any extra permissions") - print(" so it's safe to apply them.") - print() - print("The build will fail due to this.") - exit(1) - def read_current_status(current_commit, path): # type: (str, str) -> typing.Mapping[str, typing.Any] @@ -280,21 +234,6 @@ def update_latest( try: if __name__ != '__main__': exit(0) - repo = os.environ.get('TOOLSTATE_VALIDATE_MAINTAINERS_REPO') - if repo: - github_token = os.environ.get('TOOLSTATE_REPO_ACCESS_TOKEN') - if github_token: - # FIXME: This is currently broken. Starting on 2021-09-15, GitHub - # seems to have changed it so that to list the collaborators - # requires admin permissions. I think this will probably just need - # to be removed since we are probably not going to use an admin - # token, and I don't see another way to do this. - print('maintainer validation disabled') - # validate_maintainers(repo, github_token) - else: - print('skipping toolstate maintainers validation since no GitHub token is present') - # When validating maintainers don't run the full script. - exit(0) cur_commit = sys.argv[1] cur_datetime = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index d27ae416f..ec1977672 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" dependencies = [ "gimli", ] @@ -27,19 +27,10 @@ dependencies = [ ] [[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - -[[package]] name = "anyhow" -version = "1.0.65" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "anymap" @@ -49,9 +40,9 @@ checksum = "8f1f8f5a6f3d50d89e3797d7593a50f96bb2aaa20ca0cc7be1fb673232c91d72" [[package]] name = "arbitrary" -version = "1.1.7" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86fd10d912cab78764cc44307d9cd5f164e09abbeb87fb19fb6d95937e8da5f" +checksum = "b0224938f92e7aef515fac2ff2d18bd1115c1394ddf4a092e0c87e8be9499ee5" [[package]] name = "arrayvec" @@ -65,7 +56,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -78,9 +69,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ "addr2line", "cc", @@ -120,9 +111,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "camino" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e" +checksum = "c77df041dc383319cc661b428b6961a005db4d6808d5e12536931b1ca9556055" dependencies = [ "serde", ] @@ -138,22 +129,23 @@ dependencies = [ [[package]] name = "cargo_metadata" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3abb7553d5b9b8421c6de7cb02606ff15e0c6eea7d8eadd75ef013fd636bec36" +checksum = "982a0cf6a99c350d7246035613882e376d58cebe571785abc5da4f648d53ac0a" dependencies = [ "camino", "cargo-platform", "semver", "serde", "serde_json", + "thiserror", ] [[package]] name = "cc" -version = "1.0.73" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" [[package]] name = "cfg" @@ -229,9 +221,9 @@ dependencies = [ [[package]] name = "command-group" -version = "1.0.8" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7a8a86f409b4a59df3a3e4bee2de0b83f1755fdd2a25e3a9684c396fc4bed2c" +checksum = "026c3922235f9f7d78f21251a026f3acdeb7cce3deba107fe09a4bfa63d850a2" dependencies = [ "nix", "winapi", @@ -286,22 +278,22 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.11" +version = "0.9.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", + "memoffset 0.7.1", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if", ] @@ -316,14 +308,14 @@ dependencies = [ "hashbrown", "lock_api", "once_cell", - "parking_lot_core 0.9.4", + "parking_lot_core 0.9.6", ] [[package]] name = "derive_arbitrary" -version = "1.1.6" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226ad66541d865d7a7173ad6a9e691c33fdb910ac723f4bc734b3e5294a1f931" +checksum = "cf460bbff5f571bfc762da5102729f59f338be7db17a21fade44c5c4f5005350" dependencies = [ "proc-macro2", "quote", @@ -332,9 +324,9 @@ dependencies = [ [[package]] name = "dissimilar" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c97b9233581d84b8e1e689cdd3a47b6f69770084fc246e86a7f78b0d9c1d4a5" +checksum = "210ec60ae7d710bed8683e333e9d2855a8a56a3e9892b38bad3bb0d4d29b0d5e" [[package]] name = "dot" @@ -375,14 +367,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3" +checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" dependencies = [ "cfg-if", "libc", "redox_syscall", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -393,9 +385,9 @@ checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", "miniz_oxide", @@ -450,9 +442,9 @@ checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a" [[package]] name = "gimli" -version = "0.26.2" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793" [[package]] name = "hashbrown" @@ -479,6 +471,15 @@ dependencies = [ ] [[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] name = "hir" version = "0.0.0" dependencies = [ @@ -519,6 +520,7 @@ dependencies = [ "hkalbasi-rustc-ap-rustc_abi", "hkalbasi-rustc-ap-rustc_index", "indexmap", + "intern", "itertools", "la-arena", "limit", @@ -544,6 +546,7 @@ dependencies = [ "either", "expect-test", "hashbrown", + "intern", "itertools", "la-arena", "limit", @@ -574,11 +577,13 @@ dependencies = [ "hir-def", "hir-expand", "hkalbasi-rustc-ap-rustc_index", + "intern", "itertools", "la-arena", "limit", "once_cell", "profile", + "project-model", "rustc-hash", "scoped-tls", "smallvec", @@ -642,6 +647,7 @@ dependencies = [ "profile", "pulldown-cmark", "pulldown-cmark-to-cmark", + "smallvec", "stdx", "syntax", "test-utils", @@ -705,6 +711,7 @@ dependencies = [ "limit", "memchr", "once_cell", + "oorandom", "parser", "profile", "rayon", @@ -766,9 +773,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -804,6 +811,16 @@ dependencies = [ ] [[package]] +name = "intern" +version = "0.0.0" +dependencies = [ + "dashmap", + "hashbrown", + "once_cell", + "rustc-hash", +] + +[[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -814,9 +831,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "jod-thread" @@ -826,9 +843,9 @@ checksum = "8b23360e99b8717f20aaa4598f5a6541efbe30630039fbc7706cf954a87947ae" [[package]] name = "kqueue" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6112e8f37b59803ac47a42d14f1f3a59bbf72fc6857ffc5be455e28a691f8e" +checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98" dependencies = [ "kqueue-sys", "libc", @@ -856,15 +873,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.135" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libloading" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", @@ -872,11 +889,12 @@ dependencies = [ [[package]] name = "libmimalloc-sys" -version = "0.1.26" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc093ab289b0bfda3aa1bdfab9c9542be29c7ef385cfcbe77f8c9813588eb48" +checksum = "dd8c7cbf8b89019683667e347572e6d55a7df7ea36b0c4ce69961b0cde67b174" dependencies = [ "cc", + "libc", ] [[package]] @@ -915,9 +933,9 @@ dependencies = [ [[package]] name = "lsp-types" -version = "0.93.2" +version = "0.94.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be6e9c7e2d18f651974370d7aff703f9513e0df6e464fd795660edc77e6ca51" +checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237" dependencies = [ "bitflags", "serde", @@ -958,9 +976,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" +checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" dependencies = [ "libc", ] @@ -975,33 +993,42 @@ dependencies = [ ] [[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] name = "mimalloc" -version = "0.1.30" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ce6a4b40d3bff9eb3ce9881ca0737a85072f9f975886082640cd46a75cdb35" +checksum = "9dcb174b18635f7561a0c6c9fc2ce57218ac7523cf72c50af80e2d79ab8f3ba1" dependencies = [ "libmimalloc-sys", ] [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.36.1", + "windows-sys", ] [[package]] @@ -1010,20 +1037,19 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ffbca2f655e33c08be35d87278e5b18b89550a37dbd598c20db92f6a471123" dependencies = [ - "windows-sys 0.42.0", + "windows-sys", ] [[package]] name = "nix" -version = "0.22.3" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags", - "cc", "cfg-if", "libc", - "memoffset", + "static_assertions", ] [[package]] @@ -1045,29 +1071,39 @@ dependencies = [ ] [[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] name = "num_cpus" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] [[package]] name = "object" -version = "0.29.0" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "2b8c786513eb403643f2a88c244c2aaa270ef2153f55094587d0c48a3cf22a83" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "oorandom" @@ -1076,6 +1112,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] name = "parking_lot" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1083,7 +1125,7 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", ] [[package]] @@ -1093,14 +1135,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.4", + "parking_lot_core 0.9.6", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if", "instant", @@ -1112,15 +1154,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.4" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1132,13 +1174,14 @@ dependencies = [ "limit", "rustc-ap-rustc_lexer", "sourcegen", + "stdx", ] [[package]] name = "paste" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" [[package]] name = "paths" @@ -1238,9 +1281,9 @@ version = "0.0.0" [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" dependencies = [ "unicode-ident", ] @@ -1322,30 +1365,28 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" -version = "1.5.3" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" dependencies = [ - "autocfg", - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -1364,9 +1405,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "regex-syntax", ] @@ -1382,9 +1423,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "rowan" @@ -1394,7 +1435,7 @@ checksum = "5811547e7ba31e903fe48c8ceab10d40d70a101f3d15523c847cce91aa71f332" dependencies = [ "countme", "hashbrown", - "memoffset", + "memoffset 0.6.5", "rustc-hash", "text-size", ] @@ -1455,9 +1496,9 @@ dependencies = [ [[package]] name = "rustc-ap-rustc_lexer" -version = "725.0.0" +version = "727.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950742ef8a203aa7661aad3ab880438ddeb7f95d4b837c30d65db1a2c5df68e" +checksum = "8f40f26e7abdcd3b982f36c09a634cc6187988fbf6ec466c91f8d30a12ac0237" dependencies = [ "unicode-xid", ] @@ -1476,9 +1517,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "salsa" @@ -1529,9 +1570,9 @@ dependencies = [ [[package]] name = "scoped-tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" @@ -1541,27 +1582,27 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.145" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.145" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -1570,9 +1611,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.86" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ "indexmap", "itoa", @@ -1582,9 +1623,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" dependencies = [ "proc-macro2", "quote", @@ -1617,9 +1658,9 @@ dependencies = [ [[package]] name = "snap" -version = "1.0.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45456094d1983e2ee2a18fdfebce3189fa451699d0502cb8e3b49dba5ba41451" +checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" [[package]] name = "sourcegen" @@ -1629,6 +1670,12 @@ dependencies = [ ] [[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] name = "stdx" version = "0.0.0" dependencies = [ @@ -1641,9 +1688,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.102" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -1714,18 +1761,18 @@ checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a" [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -1788,10 +1835,8 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "itoa", "serde", "time-core", - "time-macros", ] [[package]] @@ -1801,15 +1846,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] -name = "time-macros" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" -dependencies = [ - "time-core", -] - -[[package]] name = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1893,12 +1929,12 @@ dependencies = [ [[package]] name = "tracing-tree" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07e90b329c621ade432823988574e820212648aa40e7a2497777d58de0fb453" +checksum = "758e983ab7c54fee18403994507e7f212b9005e957ce7984996fac8d11facedb" dependencies = [ - "ansi_term", "atty", + "nu-ansi-term", "tracing-core", "tracing-log", "tracing-subscriber", @@ -1914,9 +1950,9 @@ dependencies = [ [[package]] name = "typed-arena" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "ungrammar" @@ -1935,15 +1971,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" @@ -2064,103 +2100,60 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - -[[package]] -name = "windows-sys" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" [[package]] name = "write-json" @@ -2170,33 +2163,33 @@ checksum = "06069a848f95fceae3e5e03c0ddc8cb78452b56654ee0c8e68f938cf790fb9e3" [[package]] name = "xflags" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf19f5031a1a812e96fede16f8161218883079946cea87619d3613db1efd268" +checksum = "c4554b580522d0ca238369c16b8f6ce34524d61dafe7244993754bbd05f2c2ea" dependencies = [ "xflags-macros", ] [[package]] name = "xflags-macros" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2afbd7f2039bb6cad2dd45f0c5dff49c0d4e26118398768b7a605524d4251809" +checksum = "f58e7b3ca8977093aae6b87b6a7730216fc4c53a6530bab5c43a783cd810c1a8" [[package]] name = "xshell" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d47097dc5c85234b1e41851b3422dd6d19b3befdd35b4ae5ce386724aeca981" +checksum = "962c039b3a7b16cf4e9a4248397c6585c07547412e7d6a6e035389a802dcfe90" dependencies = [ "xshell-macros", ] [[package]] name = "xshell-macros" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88301b56c26dd9bf5c43d858538f82d6f3f7764767defbc5d34e59459901c41a" +checksum = "1dbabb1cbd15a1d6d12d9ed6b35cc6777d4af87ab3ba155ea37215f20beab80c" [[package]] name = "xtask" @@ -2204,6 +2197,7 @@ version = "0.1.0" dependencies = [ "anyhow", "flate2", + "time", "write-json", "xflags", "xshell", @@ -2212,9 +2206,9 @@ dependencies = [ [[package]] name = "zip" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537ce7411d25e54e8ae21a7ce0b15840e7bfcff15b51d697ec3266cc76bdf080" +checksum = "0445d0fbc924bb93539b4316c11afb121ea39296f99a3c4c9edad09e3658cdef" dependencies = [ "byteorder", "crc32fast", diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index 286ef1e7d..333f03ce2 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -2,6 +2,12 @@ members = ["xtask/", "lib/*", "crates/*"] exclude = ["crates/proc-macro-test/imp"] +[workspace.package] +rust-version = "1.66" +edition = "2021" +license = "MIT OR Apache-2.0" +authors = ["rust-analyzer team"] + [profile.dev] # Disabling debug info speeds up builds a bunch, # and we don't rely on it for debugging that much. @@ -32,3 +38,41 @@ debug = 0 # ungrammar = { path = "../ungrammar" } # salsa = { path = "../salsa" } + +[workspace.dependencies] +# local crates +base-db = { path = "./crates/base-db", version = "0.0.0" } +cfg = { path = "./crates/cfg", version = "0.0.0" } +flycheck = { path = "./crates/flycheck", version = "0.0.0" } +hir = { path = "./crates/hir", version = "0.0.0" } +hir-def = { path = "./crates/hir-def", version = "0.0.0" } +hir-expand = { path = "./crates/hir-expand", version = "0.0.0" } +hir-ty = { path = "./crates/hir-ty", version = "0.0.0" } +ide = { path = "./crates/ide", version = "0.0.0" } +ide-assists = { path = "./crates/ide-assists", version = "0.0.0" } +ide-completion = { path = "./crates/ide-completion", version = "0.0.0" } +ide-db = { path = "./crates/ide-db", version = "0.0.0" } +ide-diagnostics = { path = "./crates/ide-diagnostics", version = "0.0.0" } +ide-ssr = { path = "./crates/ide-ssr", version = "0.0.0" } +intern = { path = "./crates/intern", version = "0.0.0" } +limit = { path = "./crates/limit", version = "0.0.0" } +mbe = { path = "./crates/mbe", version = "0.0.0" } +parser = { path = "./crates/parser", version = "0.0.0" } +paths = { path = "./crates/paths", version = "0.0.0" } +proc-macro-api = { path = "./crates/proc-macro-api", version = "0.0.0" } +proc-macro-srv = { path = "./crates/proc-macro-srv", version = "0.0.0" } +proc-macro-srv-cli = { path = "./crates/proc-macro-srv-cli", version = "0.0.0" } +proc-macro-test = { path = "./crates/proc-macro-test", version = "0.0.0" } +profile = { path = "./crates/profile", version = "0.0.0" } +project-model = { path = "./crates/project-model", version = "0.0.0" } +sourcegen = { path = "./crates/sourcegen", version = "0.0.0" } +stdx = { path = "./crates/stdx", version = "0.0.0" } +syntax = { path = "./crates/syntax", version = "0.0.0" } +test-utils = { path = "./crates/test-utils", version = "0.0.0" } +text-edit = { path = "./crates/text-edit", version = "0.0.0" } +toolchain = { path = "./crates/toolchain", version = "0.0.0" } +tt = { path = "./crates/tt", version = "0.0.0" } +vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" } +vfs = { path = "./crates/vfs", version = "0.0.0" } +# non-local crates +smallvec = { version = "1.10.0", features = ["const_new", "union", "const_generics"] } diff --git a/src/tools/rust-analyzer/crates/base-db/Cargo.toml b/src/tools/rust-analyzer/crates/base-db/Cargo.toml index a484ecec6..f6a1075c1 100644 --- a/src/tools/rust-analyzer/crates/base-db/Cargo.toml +++ b/src/tools/rust-analyzer/crates/base-db/Cargo.toml @@ -2,9 +2,11 @@ name = "base-db" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -13,10 +15,11 @@ doctest = false salsa = "0.17.0-pre.2" rustc-hash = "1.1.0" -syntax = { path = "../syntax", version = "0.0.0" } -stdx = { path = "../stdx", version = "0.0.0" } -cfg = { path = "../cfg", version = "0.0.0" } -profile = { path = "../profile", version = "0.0.0" } -tt = { path = "../tt", version = "0.0.0" } -test-utils = { path = "../test-utils", version = "0.0.0" } -vfs = { path = "../vfs", version = "0.0.0" } +# local deps +cfg.workspace = true +profile.workspace = true +stdx.workspace = true +syntax.workspace = true +test-utils.workspace = true +tt.workspace = true +vfs.workspace = true diff --git a/src/tools/rust-analyzer/crates/base-db/src/fixture.rs b/src/tools/rust-analyzer/crates/base-db/src/fixture.rs index 6f83ea40e..8a7e9dfad 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/fixture.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/fixture.rs @@ -6,7 +6,7 @@ use rustc_hash::FxHashMap; use test_utils::{ extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER, ESCAPED_CURSOR_MARKER, }; -use tt::Subtree; +use tt::token_id::{Leaf, Subtree, TokenTree}; use vfs::{file_set::FileSet, VfsPath}; use crate::{ @@ -110,6 +110,7 @@ impl ChangeFixture { let mut crates = FxHashMap::default(); let mut crate_deps = Vec::new(); let mut default_crate_root: Option<FileId> = None; + let mut default_target_data_layout: Option<String> = None; let mut default_cfg = CfgOptions::default(); let mut file_set = FileSet::default(); @@ -162,7 +163,10 @@ impl ChangeFixture { Ok(Vec::new()), false, origin, - meta.target_data_layout.as_deref().map(Arc::from), + meta.target_data_layout + .as_deref() + .map(Arc::from) + .ok_or_else(|| "target_data_layout unset".into()), ); let prev = crates.insert(crate_name.clone(), crate_id); assert!(prev.is_none()); @@ -175,6 +179,7 @@ impl ChangeFixture { assert!(default_crate_root.is_none()); default_crate_root = Some(file_id); default_cfg = meta.cfg; + default_target_data_layout = meta.target_data_layout; } change.change_file(file_id, Some(Arc::new(text))); @@ -198,7 +203,9 @@ impl ChangeFixture { Ok(Vec::new()), false, CrateOrigin::CratesIo { repo: None, name: None }, - None, + default_target_data_layout + .map(|x| x.into()) + .ok_or_else(|| "target_data_layout unset".into()), ); } else { for (from, to, prelude) in crate_deps { @@ -212,8 +219,10 @@ impl ChangeFixture { .unwrap(); } } - let target_layout = - crate_graph.iter().next().and_then(|it| crate_graph[it].target_layout.clone()); + let target_layout = crate_graph.iter().next().map_or_else( + || Err("target_data_layout unset".into()), + |it| crate_graph[it].target_layout.clone(), + ); if let Some(mini_core) = mini_core { let core_file = file_id; @@ -301,7 +310,7 @@ impl ChangeFixture { } } -fn default_test_proc_macros() -> [(String, ProcMacro); 4] { +fn default_test_proc_macros() -> [(String, ProcMacro); 5] { [ ( r#" @@ -359,6 +368,20 @@ pub fn mirror(input: TokenStream) -> TokenStream { expander: Arc::new(MirrorProcMacroExpander), }, ), + ( + r#" +#[proc_macro] +pub fn shorten(input: TokenStream) -> TokenStream { + loop {} +} +"# + .into(), + ProcMacro { + name: "shorten".into(), + kind: crate::ProcMacroKind::FuncLike, + expander: Arc::new(ShortenProcMacroExpander), + }, + ), ] } @@ -486,17 +509,60 @@ impl ProcMacroExpander for MirrorProcMacroExpander { _: &Env, ) -> Result<Subtree, ProcMacroExpansionError> { fn traverse(input: &Subtree) -> Subtree { - let mut res = Subtree::default(); - res.delimiter = input.delimiter; + let mut token_trees = vec![]; for tt in input.token_trees.iter().rev() { let tt = match tt { tt::TokenTree::Leaf(leaf) => tt::TokenTree::Leaf(leaf.clone()), tt::TokenTree::Subtree(sub) => tt::TokenTree::Subtree(traverse(sub)), }; - res.token_trees.push(tt); + token_trees.push(tt); } - res + Subtree { delimiter: input.delimiter, token_trees } } Ok(traverse(input)) } } + +// Replaces every literal with an empty string literal and every identifier with its first letter, +// but retains all tokens' span. Useful for testing we don't assume token hasn't been modified by +// macros even if it retains its span. +#[derive(Debug)] +struct ShortenProcMacroExpander; +impl ProcMacroExpander for ShortenProcMacroExpander { + fn expand( + &self, + input: &Subtree, + _: Option<&Subtree>, + _: &Env, + ) -> Result<Subtree, ProcMacroExpansionError> { + return Ok(traverse(input)); + + fn traverse(input: &Subtree) -> Subtree { + let token_trees = input + .token_trees + .iter() + .map(|it| match it { + TokenTree::Leaf(leaf) => tt::TokenTree::Leaf(modify_leaf(leaf)), + TokenTree::Subtree(subtree) => tt::TokenTree::Subtree(traverse(subtree)), + }) + .collect(); + Subtree { delimiter: input.delimiter, token_trees } + } + + fn modify_leaf(leaf: &Leaf) -> Leaf { + let mut leaf = leaf.clone(); + match &mut leaf { + Leaf::Literal(it) => { + // XXX Currently replaces any literals with an empty string, but supporting + // "shortening" other literals would be nice. + it.text = "\"\"".into(); + } + Leaf::Punct(_) => {} + Leaf::Ident(it) => { + it.text = it.text.chars().take(1).collect(); + } + } + leaf + } + } +} diff --git a/src/tools/rust-analyzer/crates/base-db/src/input.rs b/src/tools/rust-analyzer/crates/base-db/src/input.rs index 5fa4a8024..43388e915 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/input.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/input.rs @@ -12,7 +12,7 @@ use cfg::CfgOptions; use rustc_hash::FxHashMap; use stdx::hash::{NoHashHashMap, NoHashHashSet}; use syntax::SmolStr; -use tt::Subtree; +use tt::token_id::Subtree; use vfs::{file_set::FileSet, AnchoredPath, FileId, VfsPath}; /// Files are grouped into source roots. A source root is a directory on the @@ -84,15 +84,10 @@ pub struct CrateGraph { arena: NoHashHashMap<CrateId, CrateData>, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct CrateId(pub u32); impl stdx::hash::NoHashHashable for CrateId {} -impl std::hash::Hash for CrateId { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - self.0.hash(state); - } -} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct CrateName(SmolStr); @@ -248,6 +243,7 @@ pub enum ProcMacroExpansionError { } pub type ProcMacroLoadResult = Result<Vec<ProcMacro>, String>; +pub type TargetLayoutLoadResult = Result<Arc<str>, Arc<str>>; #[derive(Debug, Clone)] pub struct ProcMacro { @@ -270,7 +266,7 @@ pub struct CrateData { pub display_name: Option<CrateDisplayName>, pub cfg_options: CfgOptions, pub potential_cfg_options: CfgOptions, - pub target_layout: Option<Arc<str>>, + pub target_layout: TargetLayoutLoadResult, pub env: Env, pub dependencies: Vec<Dependency>, pub proc_macro: ProcMacroLoadResult, @@ -286,7 +282,7 @@ pub enum Edition { } impl Edition { - pub const CURRENT: Edition = Edition::Edition2018; + pub const CURRENT: Edition = Edition::Edition2021; } #[derive(Default, Debug, Clone, PartialEq, Eq)] @@ -329,7 +325,7 @@ impl CrateGraph { proc_macro: ProcMacroLoadResult, is_proc_macro: bool, origin: CrateOrigin, - target_layout: Option<Arc<str>>, + target_layout: Result<Arc<str>, Arc<str>>, ) -> CrateId { let data = CrateData { root_file_id, @@ -652,7 +648,7 @@ mod tests { Ok(Vec::new()), false, CrateOrigin::CratesIo { repo: None, name: None }, - None, + Err("".into()), ); let crate2 = graph.add_crate_root( FileId(2u32), @@ -665,7 +661,7 @@ mod tests { Ok(Vec::new()), false, CrateOrigin::CratesIo { repo: None, name: None }, - None, + Err("".into()), ); let crate3 = graph.add_crate_root( FileId(3u32), @@ -678,7 +674,7 @@ mod tests { Ok(Vec::new()), false, CrateOrigin::CratesIo { repo: None, name: None }, - None, + Err("".into()), ); assert!(graph .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2)) @@ -705,7 +701,7 @@ mod tests { Ok(Vec::new()), false, CrateOrigin::CratesIo { repo: None, name: None }, - None, + Err("".into()), ); let crate2 = graph.add_crate_root( FileId(2u32), @@ -718,7 +714,7 @@ mod tests { Ok(Vec::new()), false, CrateOrigin::CratesIo { repo: None, name: None }, - None, + Err("".into()), ); assert!(graph .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2)) @@ -742,7 +738,7 @@ mod tests { Ok(Vec::new()), false, CrateOrigin::CratesIo { repo: None, name: None }, - None, + Err("".into()), ); let crate2 = graph.add_crate_root( FileId(2u32), @@ -755,7 +751,7 @@ mod tests { Ok(Vec::new()), false, CrateOrigin::CratesIo { repo: None, name: None }, - None, + Err("".into()), ); let crate3 = graph.add_crate_root( FileId(3u32), @@ -768,7 +764,7 @@ mod tests { Ok(Vec::new()), false, CrateOrigin::CratesIo { repo: None, name: None }, - None, + Err("".into()), ); assert!(graph .add_dep(crate1, Dependency::new(CrateName::new("crate2").unwrap(), crate2)) @@ -792,7 +788,7 @@ mod tests { Ok(Vec::new()), false, CrateOrigin::CratesIo { repo: None, name: None }, - None, + Err("".into()), ); let crate2 = graph.add_crate_root( FileId(2u32), @@ -805,7 +801,7 @@ mod tests { Ok(Vec::new()), false, CrateOrigin::CratesIo { repo: None, name: None }, - None, + Err("".into()), ); assert!(graph .add_dep( diff --git a/src/tools/rust-analyzer/crates/base-db/src/lib.rs b/src/tools/rust-analyzer/crates/base-db/src/lib.rs index 55a51d3bb..9720db9d8 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/lib.rs @@ -17,6 +17,7 @@ pub use crate::{ CrateData, CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Edition, Env, LangCrateOrigin, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroId, ProcMacroKind, ProcMacroLoadResult, SourceRoot, SourceRootId, + TargetLayoutLoadResult, }, }; pub use salsa::{self, Cancelled}; diff --git a/src/tools/rust-analyzer/crates/cfg/Cargo.toml b/src/tools/rust-analyzer/crates/cfg/Cargo.toml index 2857420c2..0880bc239 100644 --- a/src/tools/rust-analyzer/crates/cfg/Cargo.toml +++ b/src/tools/rust-analyzer/crates/cfg/Cargo.toml @@ -2,9 +2,11 @@ name = "cfg" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -12,15 +14,18 @@ doctest = false [dependencies] rustc-hash = "1.1.0" -tt = { path = "../tt", version = "0.0.0" } +# locals deps +tt.workspace = true [dev-dependencies] -mbe = { path = "../mbe" } -syntax = { path = "../syntax" } expect-test = "1.4.0" oorandom = "11.1.3" # We depend on both individually instead of using `features = ["derive"]` to microoptimize the # build graph: if the feature was enabled, syn would be built early on in the graph if `smolstr` # supports `arbitrary`. This way, we avoid feature unification. -arbitrary = "1.1.7" -derive_arbitrary = "1.1.6" +arbitrary = "1.2.2" +derive_arbitrary = "1.2.2" + +# local deps +mbe.workspace = true +syntax.workspace = true diff --git a/src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs b/src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs index 5f4eefa83..fb7505ba2 100644 --- a/src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs +++ b/src/tools/rust-analyzer/crates/cfg/src/cfg_expr.rs @@ -66,7 +66,7 @@ impl From<CfgAtom> for CfgExpr { } impl CfgExpr { - pub fn parse(tt: &tt::Subtree) -> CfgExpr { + pub fn parse<S>(tt: &tt::Subtree<S>) -> CfgExpr { next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid) } /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates. @@ -85,7 +85,7 @@ impl CfgExpr { } } -fn next_cfg_expr(it: &mut SliceIter<'_, tt::TokenTree>) -> Option<CfgExpr> { +fn next_cfg_expr<S>(it: &mut SliceIter<'_, tt::TokenTree<S>>) -> Option<CfgExpr> { let name = match it.next() { None => return None, Some(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => ident.text.clone(), diff --git a/src/tools/rust-analyzer/crates/flycheck/Cargo.toml b/src/tools/rust-analyzer/crates/flycheck/Cargo.toml index 514d567fc..609d18c4e 100644 --- a/src/tools/rust-analyzer/crates/flycheck/Cargo.toml +++ b/src/tools/rust-analyzer/crates/flycheck/Cargo.toml @@ -2,9 +2,11 @@ name = "flycheck" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -17,8 +19,9 @@ rustc-hash = "1.1.0" serde = { version = "1.0.137", features = ["derive"] } serde_json = "1.0.86" jod-thread = "0.1.2" -command-group = "1.0.8" +command-group = "2.0.1" -toolchain = { path = "../toolchain", version = "0.0.0" } -stdx = { path = "../stdx", version = "0.0.0" } -paths = { path = "../paths", version = "0.0.0" } +# local deps +paths.workspace = true +stdx.workspace = true +toolchain.workspace = true diff --git a/src/tools/rust-analyzer/crates/hir-def/Cargo.toml b/src/tools/rust-analyzer/crates/hir-def/Cargo.toml index 698be7665..31d4018d2 100644 --- a/src/tools/rust-analyzer/crates/hir-def/Cargo.toml +++ b/src/tools/rust-analyzer/crates/hir-def/Cargo.toml @@ -2,9 +2,11 @@ name = "hir-def" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -23,23 +25,28 @@ hashbrown = { version = "0.12.1", default-features = false } indexmap = "1.9.1" itertools = "0.10.5" la-arena = { version = "0.3.0", path = "../../lib/la-arena" } -once_cell = "1.15.0" +once_cell = "1.17.0" rustc-hash = "1.1.0" -smallvec = "1.10.0" +smallvec.workspace = true tracing = "0.1.35" -stdx = { path = "../stdx", version = "0.0.0" } -base-db = { path = "../base-db", version = "0.0.0" } -syntax = { path = "../syntax", version = "0.0.0" } -profile = { path = "../profile", version = "0.0.0" } -hir-expand = { path = "../hir-expand", version = "0.0.0" } rustc_abi = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_abi", default-features = false } rustc_index = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_index", default-features = false } -mbe = { path = "../mbe", version = "0.0.0" } -cfg = { path = "../cfg", version = "0.0.0" } -tt = { path = "../tt", version = "0.0.0" } -limit = { path = "../limit", version = "0.0.0" } + +# local deps +stdx.workspace = true +intern.workspace = true +base-db.workspace = true +syntax.workspace = true +profile.workspace = true +hir-expand.workspace = true +mbe.workspace = true +cfg.workspace = true +tt.workspace = true +limit.workspace = true [dev-dependencies] -test-utils = { path = "../test-utils" } expect-test = "1.4.0" + +# local deps +test-utils.workspace = true diff --git a/src/tools/rust-analyzer/crates/hir-def/src/adt.rs b/src/tools/rust-analyzer/crates/hir-def/src/adt.rs index db3b41948..9bc1c54a3 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/adt.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/adt.rs @@ -3,33 +3,34 @@ use std::sync::Arc; use base_db::CrateId; +use cfg::CfgOptions; use either::Either; + use hir_expand::{ name::{AsName, Name}, HirFileId, InFile, }; +use intern::Interned; use la_arena::{Arena, ArenaMap}; use rustc_abi::{Integer, IntegerType}; use syntax::ast::{self, HasName, HasVisibility}; -use tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree}; use crate::{ body::{CfgExpander, LowerCtx}, builtin_type::{BuiltinInt, BuiltinUint}, db::DefDatabase, - intern::Interned, item_tree::{AttrOwner, Field, FieldAstId, Fields, ItemTree, ModItem, RawVisibilityId}, layout::{Align, ReprFlags, ReprOptions}, nameres::diagnostics::DefDiagnostic, src::HasChildSource, src::HasSource, trace::Trace, + tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree}, type_ref::TypeRef, visibility::RawVisibility, EnumId, LocalEnumVariantId, LocalFieldId, LocalModuleId, Lookup, ModuleId, StructId, UnionId, VariantId, }; -use cfg::CfgOptions; /// Note that we use `StructData` for unions as well! #[derive(Debug, Clone, PartialEq, Eq)] @@ -82,7 +83,7 @@ fn repr_from_value( fn parse_repr_tt(tt: &Subtree) -> Option<ReprOptions> { match tt.delimiter { - Some(Delimiter { kind: DelimiterKind::Parenthesis, .. }) => {} + Delimiter { kind: DelimiterKind::Parenthesis, .. } => {} _ => return None, } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attr.rs b/src/tools/rust-analyzer/crates/hir-def/src/attr.rs index ab5d180e1..fcd92ad33 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attr.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attr.rs @@ -1,27 +1,26 @@ //! A higher level attributes based on TokenTree, with also some shortcuts. -use std::{fmt, hash::Hash, ops, sync::Arc}; +use std::{hash::Hash, ops, sync::Arc}; use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; use either::Either; -use hir_expand::{hygiene::Hygiene, name::AsName, HirFileId, InFile}; +use hir_expand::{ + attrs::{collect_attrs, Attr, AttrId, RawAttrs}, + HirFileId, InFile, +}; use itertools::Itertools; use la_arena::{ArenaMap, Idx, RawIdx}; -use mbe::{syntax_node_to_token_tree, DelimiterKind, Punct}; -use smallvec::{smallvec, SmallVec}; +use mbe::DelimiterKind; use syntax::{ - ast::{self, AstNode, HasAttrs, IsString}, - match_ast, AstPtr, AstToken, SmolStr, SyntaxNode, TextRange, TextSize, + ast::{self, HasAttrs, IsString}, + AstPtr, AstToken, SmolStr, TextRange, TextSize, }; -use tt::Subtree; use crate::{ db::DefDatabase, - intern::Interned, item_tree::{AttrOwner, Fields, ItemTreeId, ItemTreeNode}, nameres::{ModuleOrigin, ModuleSource}, - path::{ModPath, PathKind}, src::{HasChildSource, HasSource}, AdtId, AttrDefId, EnumId, GenericParamId, LocalEnumVariantId, LocalFieldId, Lookup, MacroId, VariantId, @@ -47,12 +46,6 @@ impl From<Documentation> for String { } } -/// Syntactical attributes, without filtering of `cfg_attr`s. -#[derive(Default, Debug, Clone, PartialEq, Eq)] -pub(crate) struct RawAttrs { - entries: Option<Arc<[Attr]>>, -} - #[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct Attrs(RawAttrs); @@ -62,30 +55,21 @@ pub struct AttrsWithOwner { owner: AttrDefId, } -impl ops::Deref for RawAttrs { - type Target = [Attr]; - - fn deref(&self) -> &[Attr] { - match &self.entries { - Some(it) => &*it, - None => &[], - } - } -} impl Attrs { pub fn get(&self, id: AttrId) -> Option<&Attr> { (**self).iter().find(|attr| attr.id == id) } + + pub(crate) fn filter(db: &dyn DefDatabase, krate: CrateId, raw_attrs: RawAttrs) -> Attrs { + Attrs(raw_attrs.filter(db.upcast(), krate)) + } } impl ops::Deref for Attrs { type Target = [Attr]; fn deref(&self) -> &[Attr] { - match &self.0.entries { - Some(it) => &*it, - None => &[], - } + &self.0 } } @@ -97,114 +81,6 @@ impl ops::Deref for AttrsWithOwner { } } -impl RawAttrs { - pub(crate) const EMPTY: Self = Self { entries: None }; - - pub(crate) fn new(db: &dyn DefDatabase, owner: &dyn ast::HasAttrs, hygiene: &Hygiene) -> Self { - let entries = collect_attrs(owner) - .filter_map(|(id, attr)| match attr { - Either::Left(attr) => { - attr.meta().and_then(|meta| Attr::from_src(db, meta, hygiene, id)) - } - Either::Right(comment) => comment.doc_comment().map(|doc| Attr { - id, - input: Some(Interned::new(AttrInput::Literal(SmolStr::new(doc)))), - path: Interned::new(ModPath::from(hir_expand::name!(doc))), - }), - }) - .collect::<Arc<_>>(); - - Self { entries: if entries.is_empty() { None } else { Some(entries) } } - } - - fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn ast::HasAttrs>) -> Self { - let hygiene = Hygiene::new(db.upcast(), owner.file_id); - Self::new(db, owner.value, &hygiene) - } - - pub(crate) fn merge(&self, other: Self) -> Self { - // FIXME: This needs to fixup `AttrId`s - match (&self.entries, other.entries) { - (None, None) => Self::EMPTY, - (None, entries @ Some(_)) => Self { entries }, - (Some(entries), None) => Self { entries: Some(entries.clone()) }, - (Some(a), Some(b)) => { - let last_ast_index = a.last().map_or(0, |it| it.id.ast_index + 1); - Self { - entries: Some( - a.iter() - .cloned() - .chain(b.iter().map(|it| { - let mut it = it.clone(); - it.id.ast_index += last_ast_index; - it - })) - .collect(), - ), - } - } - } - } - - /// Processes `cfg_attr`s, returning the resulting semantic `Attrs`. - pub(crate) fn filter(self, db: &dyn DefDatabase, krate: CrateId) -> Attrs { - let has_cfg_attrs = self.iter().any(|attr| { - attr.path.as_ident().map_or(false, |name| *name == hir_expand::name![cfg_attr]) - }); - if !has_cfg_attrs { - return Attrs(self); - } - - let crate_graph = db.crate_graph(); - let new_attrs = self - .iter() - .flat_map(|attr| -> SmallVec<[_; 1]> { - let is_cfg_attr = - attr.path.as_ident().map_or(false, |name| *name == hir_expand::name![cfg_attr]); - if !is_cfg_attr { - return smallvec![attr.clone()]; - } - - let subtree = match attr.token_tree_value() { - Some(it) => it, - _ => return smallvec![attr.clone()], - }; - - // Input subtree is: `(cfg, $(attr),+)` - // Split it up into a `cfg` subtree and the `attr` subtrees. - // FIXME: There should be a common API for this. - let mut parts = subtree.token_trees.split(|tt| { - matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(Punct { char: ',', .. }))) - }); - let cfg = match parts.next() { - Some(it) => it, - None => return smallvec![], - }; - let cfg = Subtree { delimiter: subtree.delimiter, token_trees: cfg.to_vec() }; - let cfg = CfgExpr::parse(&cfg); - let index = attr.id; - let attrs = parts.filter(|a| !a.is_empty()).filter_map(|attr| { - let tree = Subtree { delimiter: None, token_trees: attr.to_vec() }; - // FIXME hygiene - let hygiene = Hygiene::new_unhygienic(); - Attr::from_tt(db, &tree, &hygiene, index) - }); - - let cfg_options = &crate_graph[krate].cfg_options; - if cfg_options.check(&cfg) == Some(false) { - smallvec![] - } else { - cov_mark::hit!(cfg_attr_active); - - attrs.collect() - } - }) - .collect(); - - Attrs(RawAttrs { entries: Some(new_attrs) }) - } -} - impl Attrs { pub const EMPTY: Self = Self(RawAttrs::EMPTY); @@ -251,19 +127,18 @@ impl Attrs { let enum_ = &item_tree[loc.id.value]; let cfg_options = &crate_graph[krate].cfg_options; - let variant = 'tri: loop { - let mut idx = 0; - for variant in enum_.variants.clone() { - let attrs = item_tree.attrs(db, krate, variant.into()); - if attrs.is_cfg_enabled(cfg_options) { - if it.local_id == Idx::from_raw(RawIdx::from(idx)) { - break 'tri variant; - } - idx += 1; - } - } + + let Some(variant) = enum_.variants.clone().filter(|variant| { + let attrs = item_tree.attrs(db, krate, (*variant).into()); + attrs.is_cfg_enabled(cfg_options) + }) + .zip(0u32..) + .find(|(_variant, idx)| it.local_id == Idx::from_raw(RawIdx::from(*idx))) + .map(|(variant, _idx)| variant) + else { return Arc::new(res); }; + (item_tree[variant].fields.clone(), item_tree, krate) } VariantId::StructId(it) => { @@ -358,7 +233,7 @@ impl Attrs { pub fn has_doc_hidden(&self) -> bool { self.by_key("doc").tt_values().any(|tt| { - tt.delimiter_kind() == Some(DelimiterKind::Parenthesis) && + tt.delimiter.kind == DelimiterKind::Parenthesis && matches!(&*tt.token_trees, [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] if ident.text == "hidden") }) } @@ -403,7 +278,7 @@ impl AttrsWithOwner { .raw_attrs(AttrOwner::ModItem(definition_tree_id.value.into())) .clone(), ModuleOrigin::BlockExpr { block } => RawAttrs::from_attrs_owner( - db, + db.upcast(), InFile::new(block.file_id, block.to_node(db.upcast())) .as_ref() .map(|it| it as &dyn ast::HasAttrs), @@ -439,7 +314,7 @@ impl AttrsWithOwner { GenericParamId::ConstParamId(it) => { let src = it.parent().child_source(db); RawAttrs::from_attrs_owner( - db, + db.upcast(), src.with_value(src.value[it.local_id()].as_ref().either( |it| match it { ast::TypeOrConstParam::Type(it) => it as _, @@ -452,7 +327,7 @@ impl AttrsWithOwner { GenericParamId::TypeParamId(it) => { let src = it.parent().child_source(db); RawAttrs::from_attrs_owner( - db, + db.upcast(), src.with_value(src.value[it.local_id()].as_ref().either( |it| match it { ast::TypeOrConstParam::Type(it) => it as _, @@ -464,14 +339,14 @@ impl AttrsWithOwner { } GenericParamId::LifetimeParamId(it) => { let src = it.parent.child_source(db); - RawAttrs::from_attrs_owner(db, src.with_value(&src.value[it.local_id])) + RawAttrs::from_attrs_owner(db.upcast(), src.with_value(&src.value[it.local_id])) } }, AttrDefId::ExternBlockId(it) => attrs_from_item_tree(it.lookup(db).id, db), }; - let attrs = raw_attrs.filter(db, def.krate(db)); - Self { attrs, owner: def } + let attrs = raw_attrs.filter(db.upcast(), def.krate(db)); + Self { attrs: Attrs(attrs), owner: def } } pub fn source_map(&self, db: &dyn DefDatabase) -> AttrSourceMap { @@ -627,40 +502,6 @@ fn doc_indent(attrs: &Attrs) -> usize { .unwrap_or(0) } -fn inner_attributes( - syntax: &SyntaxNode, -) -> Option<impl Iterator<Item = Either<ast::Attr, ast::Comment>>> { - let node = match_ast! { - match syntax { - ast::SourceFile(_) => syntax.clone(), - ast::ExternBlock(it) => it.extern_item_list()?.syntax().clone(), - ast::Fn(it) => it.body()?.stmt_list()?.syntax().clone(), - ast::Impl(it) => it.assoc_item_list()?.syntax().clone(), - ast::Module(it) => it.item_list()?.syntax().clone(), - ast::BlockExpr(it) => { - use syntax::SyntaxKind::{BLOCK_EXPR , EXPR_STMT}; - // Block expressions accept outer and inner attributes, but only when they are the outer - // expression of an expression statement or the final expression of another block expression. - let may_carry_attributes = matches!( - it.syntax().parent().map(|it| it.kind()), - Some(BLOCK_EXPR | EXPR_STMT) - ); - if !may_carry_attributes { - return None - } - syntax.clone() - }, - _ => return None, - } - }; - - let attrs = ast::AttrDocCommentIter::from_syntax_node(&node).filter(|el| match el { - Either::Left(attr) => attr.kind().is_inner(), - Either::Right(comment) => comment.is_inner(), - }); - Some(attrs) -} - #[derive(Debug)] pub struct AttrSourceMap { source: Vec<Either<ast::Attr, ast::Comment>>, @@ -703,7 +544,7 @@ impl AttrSourceMap { } fn source_of_id(&self, id: AttrId) -> InFile<&Either<ast::Attr, ast::Comment>> { - let ast_idx = id.ast_index as usize; + let ast_idx = id.ast_index(); let file_id = match self.mod_def_site_file_id { Some((file_id, def_site_cut)) if def_site_cut <= ast_idx => file_id, _ => self.file_id, @@ -779,128 +620,6 @@ fn get_doc_string_in_attr(it: &ast::Attr) -> Option<ast::String> { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct AttrId { - pub(crate) ast_index: u32, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Attr { - pub(crate) id: AttrId, - pub(crate) path: Interned<ModPath>, - pub(crate) input: Option<Interned<AttrInput>>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum AttrInput { - /// `#[attr = "string"]` - Literal(SmolStr), - /// `#[attr(subtree)]` - TokenTree(tt::Subtree, mbe::TokenMap), -} - -impl fmt::Display for AttrInput { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - AttrInput::Literal(lit) => write!(f, " = \"{}\"", lit.escape_debug()), - AttrInput::TokenTree(subtree, _) => subtree.fmt(f), - } - } -} - -impl Attr { - fn from_src( - db: &dyn DefDatabase, - ast: ast::Meta, - hygiene: &Hygiene, - id: AttrId, - ) -> Option<Attr> { - let path = Interned::new(ModPath::from_src(db.upcast(), ast.path()?, hygiene)?); - let input = if let Some(ast::Expr::Literal(lit)) = ast.expr() { - let value = match lit.kind() { - ast::LiteralKind::String(string) => string.value()?.into(), - _ => lit.syntax().first_token()?.text().trim_matches('"').into(), - }; - Some(Interned::new(AttrInput::Literal(value))) - } else if let Some(tt) = ast.token_tree() { - let (tree, map) = syntax_node_to_token_tree(tt.syntax()); - Some(Interned::new(AttrInput::TokenTree(tree, map))) - } else { - None - }; - Some(Attr { id, path, input }) - } - - fn from_tt( - db: &dyn DefDatabase, - tt: &tt::Subtree, - hygiene: &Hygiene, - id: AttrId, - ) -> Option<Attr> { - let (parse, _) = mbe::token_tree_to_syntax_node(tt, mbe::TopEntryPoint::MetaItem); - let ast = ast::Meta::cast(parse.syntax_node())?; - - Self::from_src(db, ast, hygiene, id) - } - - pub fn path(&self) -> &ModPath { - &self.path - } -} - -impl Attr { - /// #[path = "string"] - pub fn string_value(&self) -> Option<&SmolStr> { - match self.input.as_deref()? { - AttrInput::Literal(it) => Some(it), - _ => None, - } - } - - /// #[path(ident)] - pub fn single_ident_value(&self) -> Option<&tt::Ident> { - match self.input.as_deref()? { - AttrInput::TokenTree(subtree, _) => match &*subtree.token_trees { - [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] => Some(ident), - _ => None, - }, - _ => None, - } - } - - /// #[path TokenTree] - pub fn token_tree_value(&self) -> Option<&Subtree> { - match self.input.as_deref()? { - AttrInput::TokenTree(subtree, _) => Some(subtree), - _ => None, - } - } - - /// Parses this attribute as a token tree consisting of comma separated paths. - pub fn parse_path_comma_token_tree(&self) -> Option<impl Iterator<Item = ModPath> + '_> { - let args = self.token_tree_value()?; - - if args.delimiter_kind() != Some(DelimiterKind::Parenthesis) { - return None; - } - let paths = args - .token_trees - .split(|tt| matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(Punct { char: ',', .. })))) - .filter_map(|tts| { - if tts.is_empty() { - return None; - } - let segments = tts.iter().filter_map(|tt| match tt { - tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => Some(id.as_name()), - _ => None, - }); - Some(ModPath::from_segments(PathKind::Plain, segments)) - }); - - Some(paths) - } -} - #[derive(Debug, Clone, Copy)] pub struct AttrQuery<'attr> { attrs: &'attr Attrs, @@ -908,7 +627,7 @@ pub struct AttrQuery<'attr> { } impl<'attr> AttrQuery<'attr> { - pub fn tt_values(self) -> impl Iterator<Item = &'attr Subtree> { + pub fn tt_values(self) -> impl Iterator<Item = &'attr crate::tt::Subtree> { self.attrs().filter_map(|attr| attr.token_tree_value()) } @@ -953,21 +672,6 @@ fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase tree.raw_attrs(mod_item.into()).clone() } -fn collect_attrs( - owner: &dyn ast::HasAttrs, -) -> impl Iterator<Item = (AttrId, Either<ast::Attr, ast::Comment>)> { - let inner_attrs = inner_attributes(owner.syntax()).into_iter().flatten(); - let outer_attrs = - ast::AttrDocCommentIter::from_syntax_node(owner.syntax()).filter(|el| match el { - Either::Left(attr) => attr.kind().is_outer(), - Either::Right(comment) => comment.is_outer(), - }); - outer_attrs - .chain(inner_attrs) - .enumerate() - .map(|(id, attr)| (AttrId { ast_index: id as u32 }, attr)) -} - pub(crate) fn variants_attrs_source_map( db: &dyn DefDatabase, def: EnumId, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body.rs b/src/tools/rust-analyzer/crates/hir-def/src/body.rs index 78fbaa9d7..8fd9255b8 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body.rs @@ -12,15 +12,17 @@ use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; use drop_bomb::DropBomb; use either::Either; -use hir_expand::{hygiene::Hygiene, ExpandError, ExpandResult, HirFileId, InFile, MacroCallId}; +use hir_expand::{ + attrs::RawAttrs, hygiene::Hygiene, ExpandError, ExpandResult, HirFileId, InFile, MacroCallId, +}; use la_arena::{Arena, ArenaMap}; use limit::Limit; use profile::Count; use rustc_hash::FxHashMap; -use syntax::{ast, AstPtr, SyntaxNodePtr}; +use syntax::{ast, AstPtr, SyntaxNode, SyntaxNodePtr}; use crate::{ - attr::{Attrs, RawAttrs}, + attr::Attrs, db::DefDatabase, expr::{dummy_expr_id, Expr, ExprId, Label, LabelId, Pat, PatId}, item_scope::BuiltinShadowMode, @@ -49,7 +51,8 @@ pub struct Expander { def_map: Arc<DefMap>, current_file_id: HirFileId, module: LocalModuleId, - recursion_limit: usize, + /// `recursion_depth == usize::MAX` indicates that the recursion limit has been reached. + recursion_depth: usize, } impl CfgExpander { @@ -64,7 +67,7 @@ impl CfgExpander { } pub(crate) fn parse_attrs(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> Attrs { - RawAttrs::new(db, owner, &self.hygiene).filter(db, self.krate) + Attrs::filter(db, self.krate, RawAttrs::new(db.upcast(), owner, &self.hygiene)) } pub(crate) fn is_cfg_enabled(&self, db: &dyn DefDatabase, owner: &dyn ast::HasAttrs) -> bool { @@ -82,7 +85,7 @@ impl Expander { def_map, current_file_id, module: module.local_id, - recursion_limit: 0, + recursion_depth: 0, } } @@ -91,31 +94,37 @@ impl Expander { db: &dyn DefDatabase, macro_call: ast::MacroCall, ) -> Result<ExpandResult<Option<(Mark, T)>>, UnresolvedMacro> { - if self.recursion_limit(db).check(self.recursion_limit + 1).is_err() { - cov_mark::hit!(your_stack_belongs_to_me); - return Ok(ExpandResult::only_err(ExpandError::Other( - "reached recursion limit during macro expansion".into(), - ))); + let mut unresolved_macro_err = None; + + let result = self.within_limit(db, |this| { + let macro_call = InFile::new(this.current_file_id, ¯o_call); + + let resolver = + |path| this.resolve_path_as_macro(db, &path).map(|it| macro_id_to_def_id(db, it)); + + let mut err = None; + let call_id = match macro_call.as_call_id_with_errors( + db, + this.def_map.krate(), + resolver, + &mut |e| { + err.get_or_insert(e); + }, + ) { + Ok(call_id) => call_id, + Err(resolve_err) => { + unresolved_macro_err = Some(resolve_err); + return ExpandResult { value: None, err: None }; + } + }; + ExpandResult { value: call_id.ok(), err } + }); + + if let Some(err) = unresolved_macro_err { + Err(err) + } else { + Ok(result) } - - let macro_call = InFile::new(self.current_file_id, ¯o_call); - - let resolver = - |path| self.resolve_path_as_macro(db, &path).map(|it| macro_id_to_def_id(db, it)); - - let mut err = None; - let call_id = - macro_call.as_call_id_with_errors(db, self.def_map.krate(), resolver, &mut |e| { - err.get_or_insert(e); - })?; - let call_id = match call_id { - Ok(it) => it, - Err(_) => { - return Ok(ExpandResult { value: None, err }); - } - }; - - Ok(self.enter_expand_inner(db, call_id, err)) } pub fn enter_expand_id<T: ast::AstNode>( @@ -123,15 +132,14 @@ impl Expander { db: &dyn DefDatabase, call_id: MacroCallId, ) -> ExpandResult<Option<(Mark, T)>> { - self.enter_expand_inner(db, call_id, None) + self.within_limit(db, |_this| ExpandResult::ok(Some(call_id))) } - fn enter_expand_inner<T: ast::AstNode>( - &mut self, + fn enter_expand_inner( db: &dyn DefDatabase, call_id: MacroCallId, mut err: Option<ExpandError>, - ) -> ExpandResult<Option<(Mark, T)>> { + ) -> ExpandResult<Option<(HirFileId, SyntaxNode)>> { if err.is_none() { err = db.macro_expand_error(call_id); } @@ -152,29 +160,21 @@ impl Expander { } }; - let node = match T::cast(raw_node) { - Some(it) => it, - None => { - // This can happen without being an error, so only forward previous errors. - return ExpandResult { value: None, err }; - } - }; - - tracing::debug!("macro expansion {:#?}", node.syntax()); - - self.recursion_limit += 1; - let mark = - Mark { file_id: self.current_file_id, bomb: DropBomb::new("expansion mark dropped") }; - self.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id); - self.current_file_id = file_id; - - ExpandResult { value: Some((mark, node)), err } + ExpandResult { value: Some((file_id, raw_node)), err } } pub fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) { self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id); self.current_file_id = mark.file_id; - self.recursion_limit -= 1; + if self.recursion_depth == usize::MAX { + // Recursion limit has been reached somewhere in the macro expansion tree. Reset the + // depth only when we get out of the tree. + if !self.current_file_id.is_macro() { + self.recursion_depth = 0; + } + } else { + self.recursion_depth -= 1; + } mark.bomb.defuse(); } @@ -213,6 +213,50 @@ impl Expander { #[cfg(test)] return Limit::new(std::cmp::min(32, limit)); } + + fn within_limit<F, T: ast::AstNode>( + &mut self, + db: &dyn DefDatabase, + op: F, + ) -> ExpandResult<Option<(Mark, T)>> + where + F: FnOnce(&mut Self) -> ExpandResult<Option<MacroCallId>>, + { + if self.recursion_depth == usize::MAX { + // Recursion limit has been reached somewhere in the macro expansion tree. We should + // stop expanding other macro calls in this tree, or else this may result in + // exponential number of macro expansions, leading to a hang. + // + // The overflow error should have been reported when it occurred (see the next branch), + // so don't return overflow error here to avoid diagnostics duplication. + cov_mark::hit!(overflow_but_not_me); + return ExpandResult::only_err(ExpandError::RecursionOverflowPosioned); + } else if self.recursion_limit(db).check(self.recursion_depth + 1).is_err() { + self.recursion_depth = usize::MAX; + cov_mark::hit!(your_stack_belongs_to_me); + return ExpandResult::only_err(ExpandError::Other( + "reached recursion limit during macro expansion".into(), + )); + } + + let ExpandResult { value, err } = op(self); + let Some(call_id) = value else { + return ExpandResult { value: None, err }; + }; + + Self::enter_expand_inner(db, call_id, err).map(|value| { + value.and_then(|(new_file_id, node)| { + let node = T::cast(node)?; + + self.recursion_depth += 1; + self.cfg_expander.hygiene = Hygiene::new(db.upcast(), new_file_id); + let old_file_id = std::mem::replace(&mut self.current_file_id, new_file_id); + let mark = + Mark { file_id: old_file_id, bomb: DropBomb::new("expansion mark dropped") }; + Some((mark, node)) + }) + }) + } } #[derive(Debug)] diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs index e8da24e3a..04b1c4f01 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs @@ -10,6 +10,7 @@ use hir_expand::{ name::{name, AsName, Name}, AstId, ExpandError, HirFileId, InFile, }; +use intern::Interned; use la_arena::Arena; use once_cell::unsync::OnceCell; use profile::Count; @@ -33,7 +34,6 @@ use crate::{ Label, LabelId, Literal, MatchArm, Movability, Pat, PatId, RecordFieldPat, RecordLitField, Statement, }, - intern::Interned, item_scope::BuiltinShadowMode, path::{GenericArgs, Path}, type_ref::{Mutability, Rawness, TypeRef}, @@ -67,9 +67,9 @@ impl<'a> LowerCtx<'a> { Path::from_src(ast, self) } - pub(crate) fn ast_id<N: AstNode>(&self, db: &dyn DefDatabase, item: &N) -> Option<AstId<N>> { + pub(crate) fn ast_id<N: AstNode>(&self, item: &N) -> Option<AstId<N>> { let &(file_id, ref ast_id_map) = self.ast_id_map.as_ref()?; - let ast_id_map = ast_id_map.get_or_init(|| db.ast_id_map(file_id)); + let ast_id_map = ast_id_map.get_or_init(|| self.db.ast_id_map(file_id)); Some(InFile::new(file_id, ast_id_map.ast_id(item))) } } @@ -624,6 +624,10 @@ impl ExprCollector<'_> { krate: *krate, }); } + Some(ExpandError::RecursionOverflowPosioned) => { + // Recursion limit has been reached in the macro expansion tree, but not in + // this very macro call. Don't add diagnostics to avoid duplication. + } Some(err) => { self.source_map.diagnostics.push(BodyDiagnostic::MacroError { node: InFile::new(outer_file, syntax_ptr), @@ -636,6 +640,8 @@ impl ExprCollector<'_> { match res.value { Some((mark, expansion)) => { + // Keep collecting even with expansion errors so we can provide completions and + // other services in incomplete macro expressions. self.source_map.expansions.insert(macro_call_ptr, self.expander.current_file_id); let prev_ast_id_map = mem::replace( &mut self.ast_id_map, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs index 10b9b26bb..4b4664a1c 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs @@ -80,7 +80,7 @@ impl<'a> Write for Printer<'a> { fn write_str(&mut self, s: &str) -> fmt::Result { for line in s.split_inclusive('\n') { if self.needs_indent { - match self.buf.chars().rev().skip_while(|ch| *ch == ' ').next() { + match self.buf.chars().rev().find(|ch| *ch != ' ') { Some('\n') | None => {} _ => self.buf.push('\n'), } @@ -113,7 +113,7 @@ impl<'a> Printer<'a> { } fn newline(&mut self) { - match self.buf.chars().rev().skip_while(|ch| *ch == ' ').next() { + match self.buf.chars().rev().find(|ch| *ch != ' ') { Some('\n') | None => {} _ => writeln!(self).unwrap(), } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs index c9601f855..edee2c7ff 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/tests.rs @@ -62,6 +62,19 @@ fn main() { n_nuple!(1,2,3); } } #[test] +fn your_stack_belongs_to_me2() { + cov_mark::check!(overflow_but_not_me); + lower( + r#" +macro_rules! foo { + () => {{ foo!(); foo!(); }} +} +fn main() { foo!(); } +"#, + ); +} + +#[test] fn recursion_limit() { cov_mark::check!(your_stack_belongs_to_me); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs b/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs index bb1316525..19d2fe956 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs @@ -117,7 +117,7 @@ impl ChildBySource for ItemScope { let adt = ast_id.to_node(db.upcast()); calls.for_each(|(attr_id, call_id, calls)| { if let Some(Either::Left(attr)) = - adt.doc_comments_and_attrs().nth(attr_id.ast_index as usize) + adt.doc_comments_and_attrs().nth(attr_id.ast_index()) { res[keys::DERIVE_MACRO_CALL].insert(attr, (attr_id, call_id, calls.into())); } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/data.rs b/src/tools/rust-analyzer/crates/hir-def/src/data.rs index e6b05f27a..c3c1dfd39 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/data.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/data.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use hir_expand::{name::Name, AstId, ExpandResult, HirFileId, InFile, MacroCallId, MacroDefKind}; +use intern::Interned; use smallvec::SmallVec; use syntax::ast; @@ -10,7 +11,6 @@ use crate::{ attr::Attrs, body::{Expander, Mark}, db::DefDatabase, - intern::Interned, item_tree::{self, AssocItem, FnFlags, ItemTree, ItemTreeId, ModItem, Param, TreeId}, nameres::{ attr_resolution::ResolvedAttr, @@ -142,7 +142,7 @@ impl FunctionData { } } -fn parse_rustc_legacy_const_generics(tt: &tt::Subtree) -> Box<[u32]> { +fn parse_rustc_legacy_const_generics(tt: &crate::tt::Subtree) -> Box<[u32]> { let mut indices = Vec::new(); for args in tt.token_trees.chunks(2) { match &args[0] { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/db.rs b/src/tools/rust-analyzer/crates/hir-def/src/db.rs index 431c82554..b23427a73 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/db.rs @@ -4,8 +4,9 @@ use std::sync::Arc; use base_db::{salsa, CrateId, SourceDatabase, Upcast}; use either::Either; use hir_expand::{db::AstDatabase, HirFileId}; +use intern::Interned; use la_arena::ArenaMap; -use syntax::{ast, AstPtr, SmolStr}; +use syntax::{ast, AstPtr}; use crate::{ adt::{EnumData, StructData}, @@ -17,9 +18,8 @@ use crate::{ }, generics::GenericParams, import_map::ImportMap, - intern::Interned, item_tree::{AttrOwner, ItemTree}, - lang_item::{LangItemTarget, LangItems}, + lang_item::{LangItem, LangItemTarget, LangItems}, nameres::{diagnostics::DefDiagnostic, DefMap}, visibility::{self, Visibility}, AttrDefId, BlockId, BlockLoc, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, ExternBlockId, @@ -183,7 +183,7 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> { fn crate_lang_items(&self, krate: CrateId) -> Arc<LangItems>; #[salsa::invoke(LangItems::lang_item_query)] - fn lang_item(&self, start_crate: CrateId, item: SmolStr) -> Option<LangItemTarget>; + fn lang_item(&self, start_crate: CrateId, item: LangItem) -> Option<LangItemTarget>; #[salsa::invoke(ImportMap::import_map_query)] fn import_map(&self, krate: CrateId) -> Arc<ImportMap>; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr.rs index 7b6569421..48028b7c6 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr.rs @@ -15,11 +15,11 @@ use std::fmt; use hir_expand::name::Name; +use intern::Interned; use la_arena::{Idx, RawIdx}; use crate::{ builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint}, - intern::Interned, path::{GenericArgs, Path}, type_ref::{Mutability, Rawness, TypeRef}, BlockId, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs b/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs index ddd7ad99e..3f4392320 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/find_path.rs @@ -811,7 +811,7 @@ pub struct S; fn prelude() { check_found_path( r#" -//- /main.rs crate:main deps:std +//- /main.rs edition:2018 crate:main deps:std $0 //- /std.rs crate:std pub mod prelude { @@ -852,7 +852,7 @@ pub mod prelude { fn imported_prelude() { check_found_path( r#" -//- /main.rs crate:main deps:std +//- /main.rs edition:2018 crate:main deps:std use S; $0 //- /std.rs crate:std @@ -872,7 +872,7 @@ pub mod prelude { #[test] fn enum_variant_from_prelude() { let code = r#" -//- /main.rs crate:main deps:std +//- /main.rs edition:2018 crate:main deps:std $0 //- /std.rs crate:std pub mod prelude { @@ -1273,7 +1273,7 @@ fn f() { fn prelude_with_inner_items() { check_found_path( r#" -//- /main.rs crate:main deps:std +//- /main.rs edition:2018 crate:main deps:std fn f() { fn inner() {} $0 diff --git a/src/tools/rust-analyzer/crates/hir-def/src/generics.rs b/src/tools/rust-analyzer/crates/hir-def/src/generics.rs index f74559f5d..b2ab0c30e 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/generics.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/generics.rs @@ -9,6 +9,7 @@ use hir_expand::{ name::{AsName, Name}, ExpandResult, HirFileId, InFile, }; +use intern::Interned; use la_arena::{Arena, ArenaMap, Idx}; use once_cell::unsync::Lazy; use std::ops::DerefMut; @@ -20,7 +21,6 @@ use crate::{ child_by_source::ChildBySource, db::DefDatabase, dyn_map::DynMap, - intern::Interned, keys, src::{HasChildSource, HasSource}, type_ref::{LifetimeRef, TypeBound, TypeRef}, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs index c7b213b7e..53a4173ff 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs @@ -4,7 +4,7 @@ use std::collections::hash_map::Entry; use base_db::CrateId; -use hir_expand::{name::Name, AstId, MacroCallId}; +use hir_expand::{attrs::AttrId, name::Name, AstId, MacroCallId}; use itertools::Itertools; use once_cell::sync::Lazy; use profile::Count; @@ -14,8 +14,8 @@ use stdx::format_to; use syntax::ast; use crate::{ - attr::AttrId, db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, - ConstId, HasModule, ImplId, LocalModuleId, MacroId, ModuleDefId, ModuleId, TraitId, + db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ConstId, HasModule, + ImplId, LocalModuleId, MacroId, ModuleDefId, ModuleId, TraitId, }; #[derive(Copy, Clone, Debug)] diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs index 80297f8ad..19d01630e 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs @@ -48,10 +48,12 @@ use base_db::CrateId; use either::Either; use hir_expand::{ ast_id_map::FileAstId, + attrs::RawAttrs, hygiene::Hygiene, name::{name, AsName, Name}, ExpandTo, HirFileId, InFile, }; +use intern::Interned; use la_arena::{Arena, Idx, IdxRange, RawIdx}; use profile::Count; use rustc_hash::FxHashMap; @@ -60,10 +62,9 @@ use stdx::never; use syntax::{ast, match_ast, SyntaxKind}; use crate::{ - attr::{Attrs, RawAttrs}, + attr::Attrs, db::DefDatabase, generics::GenericParams, - intern::Interned, path::{path, AssociatedTypeBinding, GenericArgs, ImportAlias, ModPath, Path, PathKind}, type_ref::{Mutability, TraitRef, TypeBound, TypeRef}, visibility::RawVisibility, @@ -110,7 +111,8 @@ impl ItemTree { Some(node) => node, None => return Default::default(), }; - if never!(syntax.kind() == SyntaxKind::ERROR) { + if never!(syntax.kind() == SyntaxKind::ERROR, "{:?} from {:?} {}", file_id, syntax, syntax) + { // FIXME: not 100% sure why these crop up, but return an empty tree to avoid a panic return Default::default(); } @@ -120,7 +122,7 @@ impl ItemTree { let mut item_tree = match_ast! { match syntax { ast::SourceFile(file) => { - top_attrs = Some(RawAttrs::new(db, &file, ctx.hygiene())); + top_attrs = Some(RawAttrs::new(db.upcast(), &file, ctx.hygiene())); ctx.lower_module_items(&file) }, ast::MacroItems(items) => { @@ -132,7 +134,7 @@ impl ItemTree { ctx.lower_macro_stmts(stmts) }, _ => { - panic!("cannot create item tree from {syntax:?} {syntax}"); + panic!("cannot create item tree for file {file_id:?} from {syntax:?} {syntax}"); }, } }; @@ -152,7 +154,11 @@ impl ItemTree { /// Returns the inner attributes of the source file. pub fn top_level_attrs(&self, db: &dyn DefDatabase, krate: CrateId) -> Attrs { - self.attrs.get(&AttrOwner::TopLevel).unwrap_or(&RawAttrs::EMPTY).clone().filter(db, krate) + Attrs::filter( + db, + krate, + self.attrs.get(&AttrOwner::TopLevel).unwrap_or(&RawAttrs::EMPTY).clone(), + ) } pub(crate) fn raw_attrs(&self, of: AttrOwner) -> &RawAttrs { @@ -160,7 +166,7 @@ impl ItemTree { } pub(crate) fn attrs(&self, db: &dyn DefDatabase, krate: CrateId, of: AttrOwner) -> Attrs { - self.raw_attrs(of).clone().filter(db, krate) + Attrs::filter(db, krate, self.raw_attrs(of).clone()) } pub fn pretty_print(&self) -> String { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs index b25274bcc..d4d3c5ef1 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs @@ -99,7 +99,7 @@ impl<'a> Ctx<'a> { } fn lower_mod_item(&mut self, item: &ast::Item) -> Option<ModItem> { - let attrs = RawAttrs::new(self.db, item, self.hygiene()); + let attrs = RawAttrs::new(self.db.upcast(), item, self.hygiene()); let item: ModItem = match item { ast::Item::Struct(ast) => self.lower_struct(ast)?.into(), ast::Item::Union(ast) => self.lower_union(ast)?.into(), @@ -173,7 +173,7 @@ impl<'a> Ctx<'a> { for field in fields.fields() { if let Some(data) = self.lower_record_field(&field) { let idx = self.data().fields.alloc(data); - self.add_attrs(idx.into(), RawAttrs::new(self.db, &field, self.hygiene())); + self.add_attrs(idx.into(), RawAttrs::new(self.db.upcast(), &field, self.hygiene())); } } let end = self.next_field_idx(); @@ -194,7 +194,7 @@ impl<'a> Ctx<'a> { for (i, field) in fields.fields().enumerate() { let data = self.lower_tuple_field(i, &field); let idx = self.data().fields.alloc(data); - self.add_attrs(idx.into(), RawAttrs::new(self.db, &field, self.hygiene())); + self.add_attrs(idx.into(), RawAttrs::new(self.db.upcast(), &field, self.hygiene())); } let end = self.next_field_idx(); IdxRange::new(start..end) @@ -239,7 +239,10 @@ impl<'a> Ctx<'a> { for variant in variants.variants() { if let Some(data) = self.lower_variant(&variant) { let idx = self.data().variants.alloc(data); - self.add_attrs(idx.into(), RawAttrs::new(self.db, &variant, self.hygiene())); + self.add_attrs( + idx.into(), + RawAttrs::new(self.db.upcast(), &variant, self.hygiene()), + ); } } let end = self.next_variant_idx(); @@ -283,7 +286,10 @@ impl<'a> Ctx<'a> { }; let ty = Interned::new(self_type); let idx = self.data().params.alloc(Param::Normal(None, ty)); - self.add_attrs(idx.into(), RawAttrs::new(self.db, &self_param, self.hygiene())); + self.add_attrs( + idx.into(), + RawAttrs::new(self.db.upcast(), &self_param, self.hygiene()), + ); has_self_param = true; } for param in param_list.params() { @@ -307,7 +313,7 @@ impl<'a> Ctx<'a> { self.data().params.alloc(Param::Normal(name, ty)) } }; - self.add_attrs(idx.into(), RawAttrs::new(self.db, ¶m, self.hygiene())); + self.add_attrs(idx.into(), RawAttrs::new(self.db.upcast(), ¶m, self.hygiene())); } } let end_param = self.next_param_idx(); @@ -442,7 +448,7 @@ impl<'a> Ctx<'a> { let items = trait_def.assoc_item_list().map(|list| { list.assoc_items() .filter_map(|item| { - let attrs = RawAttrs::new(self.db, &item, self.hygiene()); + let attrs = RawAttrs::new(self.db.upcast(), &item, self.hygiene()); self.lower_assoc_item(&item).map(|item| { self.add_attrs(ModItem::from(item).into(), attrs); item @@ -471,7 +477,7 @@ impl<'a> Ctx<'a> { .flat_map(|it| it.assoc_items()) .filter_map(|item| { let assoc = self.lower_assoc_item(&item)?; - let attrs = RawAttrs::new(self.db, &item, self.hygiene()); + let attrs = RawAttrs::new(self.db.upcast(), &item, self.hygiene()); self.add_attrs(ModItem::from(assoc).into(), attrs); Some(assoc) }) @@ -541,7 +547,7 @@ impl<'a> Ctx<'a> { // (in other words, the knowledge that they're in an extern block must not be used). // This is because an extern block can contain macros whose ItemTree's top-level items // should be considered to be in an extern block too. - let attrs = RawAttrs::new(self.db, &item, self.hygiene()); + let attrs = RawAttrs::new(self.db.upcast(), &item, self.hygiene()); let id: ModItem = match item { ast::ExternItem::Fn(ast) => self.lower_function(&ast)?.into(), ast::ExternItem::Static(ast) => self.lower_static(&ast)?.into(), @@ -653,15 +659,16 @@ fn desugar_future_path(orig: TypeRef) -> Path { let path = path![core::future::Future]; let mut generic_args: Vec<_> = std::iter::repeat(None).take(path.segments().len() - 1).collect(); - let mut last = GenericArgs::empty(); let binding = AssociatedTypeBinding { name: name![Output], args: None, type_ref: Some(orig), - bounds: Vec::new(), + bounds: Box::default(), }; - last.bindings.push(binding); - generic_args.push(Some(Interned::new(last))); + generic_args.push(Some(Interned::new(GenericArgs { + bindings: Box::new([binding]), + ..GenericArgs::empty() + }))); Path::from_known_path(path, generic_args) } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs index 48c40df22..8f230b87d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs @@ -3,7 +3,6 @@ use std::fmt::{self, Write}; use crate::{ - attr::RawAttrs, generics::{TypeOrConstParamData, WherePredicate, WherePredicateTypeTarget}, pretty::{print_path, print_type_bounds, print_type_ref}, visibility::RawVisibility, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/keys.rs b/src/tools/rust-analyzer/crates/hir-def/src/keys.rs index c5cb9a2af..72beec818 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/keys.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/keys.rs @@ -2,12 +2,11 @@ use std::marker::PhantomData; -use hir_expand::MacroCallId; +use hir_expand::{attrs::AttrId, MacroCallId}; use rustc_hash::FxHashMap; use syntax::{ast, AstNode, AstPtr}; use crate::{ - attr::AttrId, dyn_map::{DynMap, Policy}, ConstId, EnumId, EnumVariantId, FieldId, FunctionId, ImplId, LifetimeParamId, Macro2Id, MacroRulesId, ProcMacroId, StaticId, StructId, TraitId, TypeAliasId, TypeOrConstParamId, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs b/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs index 877850184..ab9bc615d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs @@ -8,19 +8,21 @@ use rustc_hash::FxHashMap; use syntax::SmolStr; use crate::{ - db::DefDatabase, AdtId, AttrDefId, CrateId, EnumId, EnumVariantId, FunctionId, ImplId, - ModuleDefId, StaticId, StructId, TraitId, + db::DefDatabase, AdtId, AssocItemId, AttrDefId, CrateId, EnumId, EnumVariantId, FunctionId, + ImplId, ModuleDefId, StaticId, StructId, TraitId, TypeAliasId, UnionId, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum LangItemTarget { EnumId(EnumId), - FunctionId(FunctionId), - ImplDefId(ImplId), - StaticId(StaticId), - StructId(StructId), - TraitId(TraitId), - EnumVariantId(EnumVariantId), + Function(FunctionId), + ImplDef(ImplId), + Static(StaticId), + Struct(StructId), + Union(UnionId), + TypeAlias(TypeAliasId), + Trait(TraitId), + EnumVariant(EnumVariantId), } impl LangItemTarget { @@ -33,42 +35,42 @@ impl LangItemTarget { pub fn as_function(self) -> Option<FunctionId> { match self { - LangItemTarget::FunctionId(id) => Some(id), + LangItemTarget::Function(id) => Some(id), _ => None, } } pub fn as_impl_def(self) -> Option<ImplId> { match self { - LangItemTarget::ImplDefId(id) => Some(id), + LangItemTarget::ImplDef(id) => Some(id), _ => None, } } pub fn as_static(self) -> Option<StaticId> { match self { - LangItemTarget::StaticId(id) => Some(id), + LangItemTarget::Static(id) => Some(id), _ => None, } } pub fn as_struct(self) -> Option<StructId> { match self { - LangItemTarget::StructId(id) => Some(id), + LangItemTarget::Struct(id) => Some(id), _ => None, } } pub fn as_trait(self) -> Option<TraitId> { match self { - LangItemTarget::TraitId(id) => Some(id), + LangItemTarget::Trait(id) => Some(id), _ => None, } } pub fn as_enum_variant(self) -> Option<EnumVariantId> { match self { - LangItemTarget::EnumVariantId(id) => Some(id), + LangItemTarget::EnumVariant(id) => Some(id), _ => None, } } @@ -76,12 +78,12 @@ impl LangItemTarget { #[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct LangItems { - items: FxHashMap<SmolStr, LangItemTarget>, + items: FxHashMap<LangItem, LangItemTarget>, } impl LangItems { - pub fn target(&self, item: &str) -> Option<LangItemTarget> { - self.items.get(item).copied() + pub fn target(&self, item: LangItem) -> Option<LangItemTarget> { + self.items.get(&item).copied() } /// Salsa query. This will look for lang items in a specific crate. @@ -94,16 +96,27 @@ impl LangItems { for (_, module_data) in crate_def_map.modules() { for impl_def in module_data.scope.impls() { - lang_items.collect_lang_item(db, impl_def, LangItemTarget::ImplDefId) + lang_items.collect_lang_item(db, impl_def, LangItemTarget::ImplDef); + for assoc in db.impl_data(impl_def).items.iter().copied() { + match assoc { + AssocItemId::FunctionId(f) => { + lang_items.collect_lang_item(db, f, LangItemTarget::Function) + } + AssocItemId::TypeAliasId(t) => { + lang_items.collect_lang_item(db, t, LangItemTarget::TypeAlias) + } + AssocItemId::ConstId(_) => (), + } + } } for def in module_data.scope.declarations() { match def { ModuleDefId::TraitId(trait_) => { - lang_items.collect_lang_item(db, trait_, LangItemTarget::TraitId); + lang_items.collect_lang_item(db, trait_, LangItemTarget::Trait); db.trait_data(trait_).items.iter().for_each(|&(_, assoc_id)| { - if let crate::AssocItemId::FunctionId(f) = assoc_id { - lang_items.collect_lang_item(db, f, LangItemTarget::FunctionId); + if let AssocItemId::FunctionId(f) = assoc_id { + lang_items.collect_lang_item(db, f, LangItemTarget::Function); } }); } @@ -113,18 +126,24 @@ impl LangItems { lang_items.collect_lang_item( db, EnumVariantId { parent: e, local_id }, - LangItemTarget::EnumVariantId, + LangItemTarget::EnumVariant, ); }); } ModuleDefId::AdtId(AdtId::StructId(s)) => { - lang_items.collect_lang_item(db, s, LangItemTarget::StructId); + lang_items.collect_lang_item(db, s, LangItemTarget::Struct); + } + ModuleDefId::AdtId(AdtId::UnionId(u)) => { + lang_items.collect_lang_item(db, u, LangItemTarget::Union); } ModuleDefId::FunctionId(f) => { - lang_items.collect_lang_item(db, f, LangItemTarget::FunctionId); + lang_items.collect_lang_item(db, f, LangItemTarget::Function); } ModuleDefId::StaticId(s) => { - lang_items.collect_lang_item(db, s, LangItemTarget::StaticId); + lang_items.collect_lang_item(db, s, LangItemTarget::Static); + } + ModuleDefId::TypeAliasId(t) => { + lang_items.collect_lang_item(db, t, LangItemTarget::TypeAlias); } _ => {} } @@ -139,7 +158,7 @@ impl LangItems { pub(crate) fn lang_item_query( db: &dyn DefDatabase, start_crate: CrateId, - item: SmolStr, + item: LangItem, ) -> Option<LangItemTarget> { let _p = profile::span("lang_item_query"); let lang_items = db.crate_lang_items(start_crate); @@ -150,7 +169,7 @@ impl LangItems { db.crate_graph()[start_crate] .dependencies .iter() - .find_map(|dep| db.lang_item(dep.crate_id, item.clone())) + .find_map(|dep| db.lang_item(dep.crate_id, item)) } fn collect_lang_item<T>( @@ -162,8 +181,8 @@ impl LangItems { T: Into<AttrDefId> + Copy, { let _p = profile::span("collect_lang_item"); - if let Some(lang_item_name) = lang_attr(db, item) { - self.items.entry(lang_item_name).or_insert_with(|| constructor(item)); + if let Some(lang_item) = lang_attr(db, item).and_then(|it| LangItem::from_str(&it)) { + self.items.entry(lang_item).or_insert_with(|| constructor(item)); } } } @@ -172,3 +191,224 @@ pub fn lang_attr(db: &dyn DefDatabase, item: impl Into<AttrDefId> + Copy) -> Opt let attrs = db.attrs(item.into()); attrs.by_key("lang").string_value().cloned() } + +pub enum GenericRequirement { + None, + Minimum(usize), + Exact(usize), +} + +macro_rules! language_item_table { + ( + $( $(#[$attr:meta])* $variant:ident, $name:ident, $method:ident, $target:expr, $generics:expr; )* + ) => { + + /// A representation of all the valid language items in Rust. + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + pub enum LangItem { + $( + #[doc = concat!("The `", stringify!($name), "` lang item.")] + $(#[$attr])* + $variant, + )* + } + + impl LangItem { + pub fn name(self) -> SmolStr { + match self { + $( LangItem::$variant => SmolStr::new(stringify!($name)), )* + } + } + + /// Opposite of [`LangItem::name`] + pub fn from_name(name: &hir_expand::name::Name) -> Option<Self> { + Self::from_str(name.as_str()?) + } + + /// Opposite of [`LangItem::name`] + pub fn from_str(name: &str) -> Option<Self> { + match name { + $( stringify!($name) => Some(LangItem::$variant), )* + _ => None, + } + } + } + } +} + +language_item_table! { +// Variant name, Name, Getter method name, Target Generic requirements; + Sized, sized, sized_trait, Target::Trait, GenericRequirement::Exact(0); + Unsize, unsize, unsize_trait, Target::Trait, GenericRequirement::Minimum(1); + /// Trait injected by `#[derive(PartialEq)]`, (i.e. "Partial EQ"). + StructuralPeq, structural_peq, structural_peq_trait, Target::Trait, GenericRequirement::None; + /// Trait injected by `#[derive(Eq)]`, (i.e. "Total EQ"; no, I will not apologize). + StructuralTeq, structural_teq, structural_teq_trait, Target::Trait, GenericRequirement::None; + Copy, copy, copy_trait, Target::Trait, GenericRequirement::Exact(0); + Clone, clone, clone_trait, Target::Trait, GenericRequirement::None; + Sync, sync, sync_trait, Target::Trait, GenericRequirement::Exact(0); + DiscriminantKind, discriminant_kind, discriminant_kind_trait, Target::Trait, GenericRequirement::None; + /// The associated item of the [`DiscriminantKind`] trait. + Discriminant, discriminant_type, discriminant_type, Target::AssocTy, GenericRequirement::None; + + PointeeTrait, pointee_trait, pointee_trait, Target::Trait, GenericRequirement::None; + Metadata, metadata_type, metadata_type, Target::AssocTy, GenericRequirement::None; + DynMetadata, dyn_metadata, dyn_metadata, Target::Struct, GenericRequirement::None; + + Freeze, freeze, freeze_trait, Target::Trait, GenericRequirement::Exact(0); + + Drop, drop, drop_trait, Target::Trait, GenericRequirement::None; + Destruct, destruct, destruct_trait, Target::Trait, GenericRequirement::None; + + CoerceUnsized, coerce_unsized, coerce_unsized_trait, Target::Trait, GenericRequirement::Minimum(1); + DispatchFromDyn, dispatch_from_dyn, dispatch_from_dyn_trait, Target::Trait, GenericRequirement::Minimum(1); + + // language items relating to transmutability + TransmuteOpts, transmute_opts, transmute_opts, Target::Struct, GenericRequirement::Exact(0); + TransmuteTrait, transmute_trait, transmute_trait, Target::Trait, GenericRequirement::Exact(3); + + Add, add, add_trait, Target::Trait, GenericRequirement::Exact(1); + Sub, sub, sub_trait, Target::Trait, GenericRequirement::Exact(1); + Mul, mul, mul_trait, Target::Trait, GenericRequirement::Exact(1); + Div, div, div_trait, Target::Trait, GenericRequirement::Exact(1); + Rem, rem, rem_trait, Target::Trait, GenericRequirement::Exact(1); + Neg, neg, neg_trait, Target::Trait, GenericRequirement::Exact(0); + Not, not, not_trait, Target::Trait, GenericRequirement::Exact(0); + BitXor, bitxor, bitxor_trait, Target::Trait, GenericRequirement::Exact(1); + BitAnd, bitand, bitand_trait, Target::Trait, GenericRequirement::Exact(1); + BitOr, bitor, bitor_trait, Target::Trait, GenericRequirement::Exact(1); + Shl, shl, shl_trait, Target::Trait, GenericRequirement::Exact(1); + Shr, shr, shr_trait, Target::Trait, GenericRequirement::Exact(1); + AddAssign, add_assign, add_assign_trait, Target::Trait, GenericRequirement::Exact(1); + SubAssign, sub_assign, sub_assign_trait, Target::Trait, GenericRequirement::Exact(1); + MulAssign, mul_assign, mul_assign_trait, Target::Trait, GenericRequirement::Exact(1); + DivAssign, div_assign, div_assign_trait, Target::Trait, GenericRequirement::Exact(1); + RemAssign, rem_assign, rem_assign_trait, Target::Trait, GenericRequirement::Exact(1); + BitXorAssign, bitxor_assign, bitxor_assign_trait, Target::Trait, GenericRequirement::Exact(1); + BitAndAssign, bitand_assign, bitand_assign_trait, Target::Trait, GenericRequirement::Exact(1); + BitOrAssign, bitor_assign, bitor_assign_trait, Target::Trait, GenericRequirement::Exact(1); + ShlAssign, shl_assign, shl_assign_trait, Target::Trait, GenericRequirement::Exact(1); + ShrAssign, shr_assign, shr_assign_trait, Target::Trait, GenericRequirement::Exact(1); + Index, index, index_trait, Target::Trait, GenericRequirement::Exact(1); + IndexMut, index_mut, index_mut_trait, Target::Trait, GenericRequirement::Exact(1); + + UnsafeCell, unsafe_cell, unsafe_cell_type, Target::Struct, GenericRequirement::None; + VaList, va_list, va_list, Target::Struct, GenericRequirement::None; + + Deref, deref, deref_trait, Target::Trait, GenericRequirement::Exact(0); + DerefMut, deref_mut, deref_mut_trait, Target::Trait, GenericRequirement::Exact(0); + DerefTarget, deref_target, deref_target, Target::AssocTy, GenericRequirement::None; + Receiver, receiver, receiver_trait, Target::Trait, GenericRequirement::None; + + Fn, fn, fn_trait, Target::Trait, GenericRequirement::Exact(1); + FnMut, fn_mut, fn_mut_trait, Target::Trait, GenericRequirement::Exact(1); + FnOnce, fn_once, fn_once_trait, Target::Trait, GenericRequirement::Exact(1); + + FnOnceOutput, fn_once_output, fn_once_output, Target::AssocTy, GenericRequirement::None; + + Future, future_trait, future_trait, Target::Trait, GenericRequirement::Exact(0); + GeneratorState, generator_state, gen_state, Target::Enum, GenericRequirement::None; + Generator, generator, gen_trait, Target::Trait, GenericRequirement::Minimum(1); + Unpin, unpin, unpin_trait, Target::Trait, GenericRequirement::None; + Pin, pin, pin_type, Target::Struct, GenericRequirement::None; + + PartialEq, eq, eq_trait, Target::Trait, GenericRequirement::Exact(1); + PartialOrd, partial_ord, partial_ord_trait, Target::Trait, GenericRequirement::Exact(1); + + // A number of panic-related lang items. The `panic` item corresponds to divide-by-zero and + // various panic cases with `match`. The `panic_bounds_check` item is for indexing arrays. + // + // The `begin_unwind` lang item has a predefined symbol name and is sort of a "weak lang item" + // in the sense that a crate is not required to have it defined to use it, but a final product + // is required to define it somewhere. Additionally, there are restrictions on crates that use + // a weak lang item, but do not have it defined. + Panic, panic, panic_fn, Target::Fn, GenericRequirement::Exact(0); + PanicNounwind, panic_nounwind, panic_nounwind, Target::Fn, GenericRequirement::Exact(0); + PanicFmt, panic_fmt, panic_fmt, Target::Fn, GenericRequirement::None; + PanicDisplay, panic_display, panic_display, Target::Fn, GenericRequirement::None; + ConstPanicFmt, const_panic_fmt, const_panic_fmt, Target::Fn, GenericRequirement::None; + PanicBoundsCheck, panic_bounds_check, panic_bounds_check_fn, Target::Fn, GenericRequirement::Exact(0); + PanicInfo, panic_info, panic_info, Target::Struct, GenericRequirement::None; + PanicLocation, panic_location, panic_location, Target::Struct, GenericRequirement::None; + PanicImpl, panic_impl, panic_impl, Target::Fn, GenericRequirement::None; + PanicCannotUnwind, panic_cannot_unwind, panic_cannot_unwind, Target::Fn, GenericRequirement::Exact(0); + /// libstd panic entry point. Necessary for const eval to be able to catch it + BeginPanic, begin_panic, begin_panic_fn, Target::Fn, GenericRequirement::None; + + ExchangeMalloc, exchange_malloc, exchange_malloc_fn, Target::Fn, GenericRequirement::None; + BoxFree, box_free, box_free_fn, Target::Fn, GenericRequirement::Minimum(1); + DropInPlace, drop_in_place, drop_in_place_fn, Target::Fn, GenericRequirement::Minimum(1); + AllocLayout, alloc_layout, alloc_layout, Target::Struct, GenericRequirement::None; + + Start, start, start_fn, Target::Fn, GenericRequirement::Exact(1); + + EhPersonality, eh_personality, eh_personality, Target::Fn, GenericRequirement::None; + EhCatchTypeinfo, eh_catch_typeinfo, eh_catch_typeinfo, Target::Static, GenericRequirement::None; + + OwnedBox, owned_box, owned_box, Target::Struct, GenericRequirement::Minimum(1); + + PhantomData, phantom_data, phantom_data, Target::Struct, GenericRequirement::Exact(1); + + ManuallyDrop, manually_drop, manually_drop, Target::Struct, GenericRequirement::None; + + MaybeUninit, maybe_uninit, maybe_uninit, Target::Union, GenericRequirement::None; + + /// Align offset for stride != 1; must not panic. + AlignOffset, align_offset, align_offset_fn, Target::Fn, GenericRequirement::None; + + Termination, termination, termination, Target::Trait, GenericRequirement::None; + + Try, Try, try_trait, Target::Trait, GenericRequirement::None; + + Tuple, tuple_trait, tuple_trait, Target::Trait, GenericRequirement::Exact(0); + + SliceLen, slice_len_fn, slice_len_fn, Target::Method(MethodKind::Inherent), GenericRequirement::None; + + // Language items from AST lowering + TryTraitFromResidual, from_residual, from_residual_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + TryTraitFromOutput, from_output, from_output_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + TryTraitBranch, branch, branch_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + TryTraitFromYeet, from_yeet, from_yeet_fn, Target::Fn, GenericRequirement::None; + + PointerSized, pointer_sized, pointer_sized, Target::Trait, GenericRequirement::Exact(0); + + Poll, Poll, poll, Target::Enum, GenericRequirement::None; + PollReady, Ready, poll_ready_variant, Target::Variant, GenericRequirement::None; + PollPending, Pending, poll_pending_variant, Target::Variant, GenericRequirement::None; + + // FIXME(swatinem): the following lang items are used for async lowering and + // should become obsolete eventually. + ResumeTy, ResumeTy, resume_ty, Target::Struct, GenericRequirement::None; + IdentityFuture, identity_future, identity_future_fn, Target::Fn, GenericRequirement::None; + GetContext, get_context, get_context_fn, Target::Fn, GenericRequirement::None; + + Context, Context, context, Target::Struct, GenericRequirement::None; + FuturePoll, poll, future_poll_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + + FromFrom, from, from_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + + OptionSome, Some, option_some_variant, Target::Variant, GenericRequirement::None; + OptionNone, None, option_none_variant, Target::Variant, GenericRequirement::None; + + ResultOk, Ok, result_ok_variant, Target::Variant, GenericRequirement::None; + ResultErr, Err, result_err_variant, Target::Variant, GenericRequirement::None; + + ControlFlowContinue, Continue, cf_continue_variant, Target::Variant, GenericRequirement::None; + ControlFlowBreak, Break, cf_break_variant, Target::Variant, GenericRequirement::None; + + IntoFutureIntoFuture, into_future, into_future_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + IntoIterIntoIter, into_iter, into_iter_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + IteratorNext, next, next_fn, Target::Method(MethodKind::Trait { body: false}), GenericRequirement::None; + + PinNewUnchecked, new_unchecked, new_unchecked_fn, Target::Method(MethodKind::Inherent), GenericRequirement::None; + + RangeFrom, RangeFrom, range_from_struct, Target::Struct, GenericRequirement::None; + RangeFull, RangeFull, range_full_struct, Target::Struct, GenericRequirement::None; + RangeInclusiveStruct, RangeInclusive, range_inclusive_struct, Target::Struct, GenericRequirement::None; + RangeInclusiveNew, range_inclusive_new, range_inclusive_new_method, Target::Method(MethodKind::Inherent), GenericRequirement::None; + Range, Range, range_struct, Target::Struct, GenericRequirement::None; + RangeToInclusive, RangeToInclusive, range_to_inclusive_struct, Target::Struct, GenericRequirement::None; + RangeTo, RangeTo, range_to_struct, Target::Struct, GenericRequirement::None; + + String, String, string, Target::Struct, GenericRequirement::None; +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/layout.rs b/src/tools/rust-analyzer/crates/hir-def/src/layout.rs index 6bb4cd94f..49b1190ad 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/layout.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/layout.rs @@ -90,6 +90,7 @@ impl IntegerExt for Integer { pub enum LayoutError { UserError(String), SizeOverflow, + TargetLayoutNotAvailable, HasPlaceholder, NotImplemented, Unknown, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs index 8267ef09c..d07c5fb67 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs @@ -28,7 +28,6 @@ pub mod dyn_map; pub mod keys; pub mod item_tree; -pub mod intern; pub mod adt; pub mod data; @@ -61,10 +60,10 @@ use std::{ sync::Arc, }; -use attr::Attr; use base_db::{impl_intern_key, salsa, CrateId, ProcMacroKind}; use hir_expand::{ ast_id_map::FileAstId, + attrs::{Attr, AttrId, AttrInput}, builtin_attr_macro::BuiltinAttrExpander, builtin_derive_macro::BuiltinDeriveExpander, builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander}, @@ -80,9 +79,10 @@ use nameres::DefMap; use stdx::impl_from; use syntax::ast; +use ::tt::token_id as tt; + use crate::{ adt::VariantData, - attr::AttrId, builtin_type::BuiltinType, item_tree::{ Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, MacroDef, MacroRules, ModItem, @@ -292,6 +292,7 @@ pub struct Macro2Loc { pub container: ModuleId, pub id: ItemTreeId<MacroDef>, pub expander: MacroExpander, + pub allow_internal_unsafe: bool, } impl_intern!(Macro2Id, Macro2Loc, intern_macro2, lookup_intern_macro2); @@ -301,8 +302,9 @@ pub struct MacroRulesId(salsa::InternId); pub struct MacroRulesLoc { pub container: ModuleId, pub id: ItemTreeId<MacroRules>, - pub local_inner: bool, pub expander: MacroExpander, + pub allow_internal_unsafe: bool, + pub local_inner: bool, } impl_intern!(MacroRulesId, MacroRulesLoc, intern_macro_rules, lookup_intern_macro_rules); @@ -896,6 +898,7 @@ pub fn macro_id_to_def_id(db: &dyn db::DefDatabase, id: MacroId) -> MacroDefId { } }, local_inner: false, + allow_internal_unsafe: loc.allow_internal_unsafe, } } MacroId::MacroRulesId(it) => { @@ -920,6 +923,7 @@ pub fn macro_id_to_def_id(db: &dyn db::DefDatabase, id: MacroId) -> MacroDefId { } }, local_inner: loc.local_inner, + allow_internal_unsafe: loc.allow_internal_unsafe, } } MacroId::ProcMacroId(it) => { @@ -935,6 +939,7 @@ pub fn macro_id_to_def_id(db: &dyn db::DefDatabase, id: MacroId) -> MacroDefId { InFile::new(loc.id.file_id(), makro.ast_id), ), local_inner: false, + allow_internal_unsafe: false, } } } @@ -943,7 +948,7 @@ pub fn macro_id_to_def_id(db: &dyn db::DefDatabase, id: MacroId) -> MacroDefId { fn derive_macro_as_call_id( db: &dyn db::DefDatabase, item_attr: &AstIdWithPath<ast::Adt>, - derive_attr: AttrId, + derive_attr_index: AttrId, derive_pos: u32, krate: CrateId, resolver: impl Fn(path::ModPath) -> Option<(MacroId, MacroDefId)>, @@ -956,7 +961,7 @@ fn derive_macro_as_call_id( MacroCallKind::Derive { ast_id: item_attr.ast_id, derive_index: derive_pos, - derive_attr_index: derive_attr.ast_index, + derive_attr_index, }, ); Ok((macro_id, def_id, call_id)) @@ -970,23 +975,33 @@ fn attr_macro_as_call_id( def: MacroDefId, is_derive: bool, ) -> MacroCallId { - let mut arg = match macro_attr.input.as_deref() { - Some(attr::AttrInput::TokenTree(tt, map)) => (tt.clone(), map.clone()), - _ => Default::default(), + let arg = match macro_attr.input.as_deref() { + Some(AttrInput::TokenTree(tt, map)) => ( + { + let mut tt = tt.clone(); + tt.delimiter = tt::Delimiter::UNSPECIFIED; + tt + }, + map.clone(), + ), + _ => (tt::Subtree::empty(), Default::default()), }; - // The parentheses are always disposed here. - arg.0.delimiter = None; - - let res = def.as_lazy_macro( + def.as_lazy_macro( db.upcast(), krate, MacroCallKind::Attr { ast_id: item_attr.ast_id, attr_args: Arc::new(arg), - invoc_attr_index: macro_attr.id.ast_index, + invoc_attr_index: macro_attr.id, is_derive, }, - ); - res -} + ) +} +intern::impl_internable!( + crate::type_ref::TypeRef, + crate::type_ref::TraitRef, + crate::type_ref::TypeBound, + crate::path::GenericArgs, + generics::GenericParams, +); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests.rs index 79c85d118..5ab90d92d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests.rs @@ -30,7 +30,7 @@ use syntax::{ SyntaxKind::{self, COMMENT, EOF, IDENT, LIFETIME_IDENT}, SyntaxNode, TextRange, T, }; -use tt::{Subtree, TokenId}; +use tt::token_id::{Subtree, TokenId}; use crate::{ db::DefDatabase, macro_id_to_def_id, nameres::ModuleSource, resolver::HasResolver, @@ -97,7 +97,9 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream let ast_id = AstId::new(source.file_id, file_ast_id.upcast()); let kind = MacroDefKind::Declarative(ast_id); - let macro_def = db.macro_def(MacroDefId { krate, kind, local_inner: false }).unwrap(); + let macro_def = db + .macro_def(MacroDefId { krate, kind, local_inner: false, allow_internal_unsafe: false }) + .unwrap(); if let TokenExpander::DeclarativeMacro { mac, def_site_token_map } = &*macro_def { let tt = match ¯o_ { ast::Macro::MacroRules(mac) => mac.token_tree().unwrap(), @@ -251,9 +253,9 @@ fn extract_id_ranges(ranges: &mut Vec<(TextRange, TokenId)>, map: &TokenMap, tre tree.token_trees.iter().for_each(|tree| match tree { tt::TokenTree::Leaf(leaf) => { let id = match leaf { - tt::Leaf::Literal(it) => it.id, - tt::Leaf::Punct(it) => it.id, - tt::Leaf::Ident(it) => it.id, + tt::Leaf::Literal(it) => it.span, + tt::Leaf::Punct(it) => it.span, + tt::Leaf::Ident(it) => it.span, }; ranges.extend(map.ranges_by_token(id, SyntaxKind::ERROR).map(|range| (range, id))); } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs index 2d5f2a692..7a3e8c3b0 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -97,6 +97,41 @@ fn#19 main#20(#21)#21 {#22 "##]], ); } +#[test] +fn float_field_acces_macro_input() { + check( + r#" +macro_rules! foo { + ($expr:expr) => { + fn foo() { + $expr; + } + }; +} +foo!(x .0.1); +foo!(x .2. 3); +foo!(x .4 .5); +"#, + expect![[r#" +macro_rules! foo { + ($expr:expr) => { + fn foo() { + $expr; + } + }; +} +fn foo() { + (x.0.1); +} +fn foo() { + (x.2.3); +} +fn foo() { + (x.4.5); +} +"#]], + ); +} #[test] fn mbe_smoke_test() { @@ -1441,7 +1476,7 @@ macro_rules! m { /* parse error: expected identifier */ /* parse error: expected SEMICOLON */ /* parse error: expected SEMICOLON */ -/* parse error: expected expression */ +/* parse error: expected expression, item or let statement */ fn f() { K::(C("0")); } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs index d2505e7ca..8358a46f0 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs @@ -830,8 +830,7 @@ macro_rules! rgb_color { /* parse error: expected COMMA */ /* parse error: expected R_ANGLE */ /* parse error: expected SEMICOLON */ -/* parse error: expected SEMICOLON */ -/* parse error: expected expression */ +/* parse error: expected expression, item or let statement */ pub fn new() { let _ = 0as u32<<(8+8); } @@ -848,21 +847,21 @@ pub fn new() { // BLOCK_EXPR@10..31 // STMT_LIST@10..31 // L_CURLY@10..11 "{" -// LET_STMT@11..27 +// LET_STMT@11..28 // LET_KW@11..14 "let" // WILDCARD_PAT@14..15 // UNDERSCORE@14..15 "_" // EQ@15..16 "=" -// CAST_EXPR@16..27 +// CAST_EXPR@16..28 // LITERAL@16..17 // INT_NUMBER@16..17 "0" // AS_KW@17..19 "as" -// PATH_TYPE@19..27 -// PATH@19..27 -// PATH_SEGMENT@19..27 +// PATH_TYPE@19..28 +// PATH@19..28 +// PATH_SEGMENT@19..28 // NAME_REF@19..22 // IDENT@19..22 "u32" -// GENERIC_ARG_LIST@22..27 +// GENERIC_ARG_LIST@22..28 // L_ANGLE@22..23 "<" // TYPE_ARG@23..27 // DYN_TRAIT_TYPE@23..27 @@ -877,9 +876,9 @@ pub fn new() { // ERROR@25..26 // INT_NUMBER@25..26 "8" // PLUS@26..27 "+" -// EXPR_STMT@27..28 -// LITERAL@27..28 -// INT_NUMBER@27..28 "8" +// CONST_ARG@27..28 +// LITERAL@27..28 +// INT_NUMBER@27..28 "8" // ERROR@28..29 // R_PAREN@28..29 ")" // SEMICOLON@29..30 ";" diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/proc_macros.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/proc_macros.rs index 118c14ed8..822bdcc12 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/proc_macros.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/proc_macros.rs @@ -104,7 +104,7 @@ macro_rules! id { $($t)* }; } -id /*+errors*/! { +id! { #[proc_macros::identity] impl Foo for WrapBj { async fn foo(&self) { @@ -113,18 +113,17 @@ id /*+errors*/! { } } "#, - expect![[r##" + expect![[r#" macro_rules! id { ($($t:tt)*) => { $($t)* }; } -/* parse error: expected SEMICOLON */ #[proc_macros::identity] impl Foo for WrapBj { async fn foo(&self ) { self .0.id().await ; } } -"##]], +"#]], ); } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs index 3650204ee..79cabeb0f 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/attr_resolution.rs @@ -1,10 +1,9 @@ //! Post-nameres attribute resolution. -use hir_expand::MacroCallId; +use hir_expand::{attrs::Attr, MacroCallId}; use syntax::{ast, SmolStr}; use crate::{ - attr::Attr, attr_macro_as_call_id, builtin_attr, db::DefDatabase, item_scope::BuiltinShadowMode, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs index 160203b77..4b39a20d8 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs @@ -10,6 +10,7 @@ use cfg::{CfgExpr, CfgOptions}; use either::Either; use hir_expand::{ ast_id_map::FileAstId, + attrs::{Attr, AttrId}, builtin_attr_macro::find_builtin_attr, builtin_derive_macro::find_builtin_derive, builtin_fn_macro::find_builtin_macro, @@ -26,7 +27,7 @@ use stdx::always; use syntax::{ast, SmolStr}; use crate::{ - attr::{Attr, AttrId, Attrs}, + attr::Attrs, attr_macro_as_call_id, db::DefDatabase, derive_macro_as_call_id, @@ -45,6 +46,7 @@ use crate::{ }, path::{ImportAlias, ModPath, PathKind}, per_ns::PerNs, + tt, visibility::{RawVisibility, Visibility}, AdtId, AstId, AstIdWithPath, ConstLoc, EnumLoc, EnumVariantId, ExternBlockLoc, FunctionId, FunctionLoc, ImplLoc, Intern, ItemContainerId, LocalModuleId, Macro2Id, Macro2Loc, @@ -82,7 +84,8 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: DefMap, tree_id: T .enumerate() .map(|(idx, it)| { // FIXME: a hacky way to create a Name from string. - let name = tt::Ident { text: it.name.clone(), id: tt::TokenId::unspecified() }; + let name = + tt::Ident { text: it.name.clone(), span: tt::TokenId::unspecified() }; ( name.as_name(), ProcMacroExpander::new(def_map.krate, base_db::ProcMacroId(idx as u32)), @@ -450,8 +453,11 @@ impl DefCollector<'_> { directive.module_id, MacroCallKind::Attr { ast_id: ast_id.ast_id, - attr_args: Default::default(), - invoc_attr_index: attr.id.ast_index, + attr_args: std::sync::Arc::new(( + tt::Subtree::empty(), + Default::default(), + )), + invoc_attr_index: attr.id, is_derive: false, }, attr.path().clone(), @@ -1406,7 +1412,7 @@ impl DefCollector<'_> { directive.module_id, MacroCallKind::Derive { ast_id: ast_id.ast_id, - derive_attr_index: derive_attr.ast_index, + derive_attr_index: *derive_attr, derive_index: *derive_pos as u32, }, ast_id.path.clone(), @@ -1599,17 +1605,15 @@ impl ModCollector<'_, '_> { FunctionLoc { container, id: ItemTreeId::new(self.tree_id, id) }.intern(db); let vis = resolve_vis(def_map, &self.item_tree[it.visibility]); - if self.def_collector.is_proc_macro { - if self.module_id == def_map.root { - if let Some(proc_macro) = attrs.parse_proc_macro_decl(&it.name) { - let crate_root = def_map.module_id(def_map.root); - self.def_collector.export_proc_macro( - proc_macro, - ItemTreeId::new(self.tree_id, id), - fn_id, - crate_root, - ); - } + if self.def_collector.is_proc_macro && self.module_id == def_map.root { + if let Some(proc_macro) = attrs.parse_proc_macro_decl(&it.name) { + let crate_root = def_map.module_id(def_map.root); + self.def_collector.export_proc_macro( + proc_macro, + ItemTreeId::new(self.tree_id, id), + fn_id, + crate_root, + ); } } @@ -1948,7 +1952,8 @@ impl ModCollector<'_, '_> { let name = match attrs.by_key("rustc_builtin_macro").string_value() { Some(it) => { // FIXME: a hacky way to create a Name from string. - name = tt::Ident { text: it.clone(), id: tt::TokenId::unspecified() }.as_name(); + name = + tt::Ident { text: it.clone(), span: tt::TokenId::unspecified() }.as_name(); &name } None => { @@ -1983,11 +1988,13 @@ impl ModCollector<'_, '_> { // Case 2: normal `macro_rules!` macro MacroExpander::Declarative }; + let allow_internal_unsafe = attrs.by_key("allow_internal_unsafe").exists(); let macro_id = MacroRulesLoc { container: module, id: ItemTreeId::new(self.tree_id, id), local_inner, + allow_internal_unsafe, expander, } .intern(self.def_collector.db); @@ -2047,10 +2054,15 @@ impl ModCollector<'_, '_> { // Case 2: normal `macro` MacroExpander::Declarative }; + let allow_internal_unsafe = attrs.by_key("allow_internal_unsafe").exists(); - let macro_id = - Macro2Loc { container: module, id: ItemTreeId::new(self.tree_id, id), expander } - .intern(self.def_collector.db); + let macro_id = Macro2Loc { + container: module, + id: ItemTreeId::new(self.tree_id, id), + expander, + allow_internal_unsafe, + } + .intern(self.def_collector.db); self.def_collector.define_macro_def( self.module_id, mac.name.clone(), diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs index 066142291..b024d7c67 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs @@ -2,12 +2,11 @@ use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; -use hir_expand::MacroCallKind; +use hir_expand::{attrs::AttrId, MacroCallKind}; use la_arena::Idx; use syntax::ast::{self, AnyHasAttrs}; use crate::{ - attr::AttrId, item_tree::{self, ItemTreeId}, nameres::LocalModuleId, path::ModPath, @@ -32,9 +31,9 @@ pub enum DefDiagnosticKind { UnimplementedBuiltinMacro { ast: AstId<ast::Macro> }, - InvalidDeriveTarget { ast: AstId<ast::Item>, id: u32 }, + InvalidDeriveTarget { ast: AstId<ast::Item>, id: usize }, - MalformedDerive { ast: AstId<ast::Adt>, id: u32 }, + MalformedDerive { ast: AstId<ast::Adt>, id: usize }, } #[derive(Debug, PartialEq, Eq)] @@ -120,7 +119,7 @@ impl DefDiagnostic { ) -> Self { Self { in_module: container, - kind: DefDiagnosticKind::InvalidDeriveTarget { ast, id: id.ast_index }, + kind: DefDiagnosticKind::InvalidDeriveTarget { ast, id: id.ast_index() }, } } @@ -131,7 +130,7 @@ impl DefDiagnostic { ) -> Self { Self { in_module: container, - kind: DefDiagnosticKind::MalformedDerive { ast, id: id.ast_index }, + kind: DefDiagnosticKind::MalformedDerive { ast, id: id.ast_index() }, } } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/mod_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/mod_resolution.rs index 4c263846d..51c565fe1 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/mod_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/mod_resolution.rs @@ -7,7 +7,7 @@ use syntax::SmolStr; use crate::{db::DefDatabase, HirFileId}; -const MOD_DEPTH_LIMIT: Limit = Limit::new(32); +static MOD_DEPTH_LIMIT: Limit = Limit::new(32); #[derive(Clone, Debug)] pub(super) struct ModDir { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/proc_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/proc_macro.rs index 06b23392c..caad4a1f3 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/proc_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/proc_macro.rs @@ -1,9 +1,9 @@ //! Nameres-specific procedural macro data and helpers. use hir_expand::name::{AsName, Name}; -use tt::{Leaf, TokenTree}; use crate::attr::Attrs; +use crate::tt::{Leaf, TokenTree}; #[derive(Debug, PartialEq, Eq)] pub struct ProcMacroDef { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs index 0d90047c2..8a27c60df 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs @@ -476,7 +476,7 @@ pub struct Bar; fn no_std_prelude() { check( r#" - //- /main.rs crate:main deps:core,std + //- /main.rs edition:2018 crate:main deps:core,std #![cfg_attr(not(never), no_std)] use Rust; @@ -544,7 +544,7 @@ fn edition_specific_preludes() { fn std_prelude_takes_precedence_above_core_prelude() { check( r#" -//- /main.rs crate:main deps:core,std +//- /main.rs edition:2018 crate:main deps:core,std use {Foo, Bar}; //- /std.rs crate:std deps:core @@ -574,7 +574,7 @@ pub mod prelude { fn cfg_not_test() { check( r#" -//- /main.rs crate:main deps:std +//- /main.rs edition:2018 crate:main deps:std use {Foo, Bar, Baz}; //- /lib.rs crate:std @@ -602,7 +602,7 @@ pub mod prelude { fn cfg_test() { check( r#" -//- /main.rs crate:main deps:std +//- /main.rs edition:2018 crate:main deps:std use {Foo, Bar, Baz}; //- /lib.rs crate:std cfg:test,feature=foo,feature=bar,opt=42 diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs index fe0ad4f38..a4ccd14cb 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs @@ -264,7 +264,7 @@ fn prelude_is_macro_use() { cov_mark::check!(prelude_is_macro_use); check( r#" -//- /main.rs crate:main deps:std +//- /main.rs edition:2018 crate:main deps:std structs!(Foo); structs_priv!(Bar); structs_outside!(Out); @@ -634,7 +634,7 @@ fn macro_dollar_crate_is_correct_in_indirect_deps() { // From std check( r#" -//- /main.rs crate:main deps:std +//- /main.rs edition:2018 crate:main deps:std foo!(); //- /std.rs crate:std deps:core @@ -1034,7 +1034,7 @@ structs!(Foo); fn macro_in_prelude() { check( r#" -//- /lib.rs crate:lib deps:std +//- /lib.rs edition:2018 crate:lib deps:std global_asm!(); //- /std.rs crate:std diff --git a/src/tools/rust-analyzer/crates/hir-def/src/path.rs b/src/tools/rust-analyzer/crates/hir-def/src/path.rs index 592223f7d..36d4c36a2 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/path.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/path.rs @@ -8,10 +8,10 @@ use std::{ use crate::{ body::LowerCtx, - intern::Interned, type_ref::{ConstScalarOrPath, LifetimeRef}, }; use hir_expand::name::Name; +use intern::Interned; use syntax::ast; use crate::type_ref::{TypeBound, TypeRef}; @@ -38,18 +38,18 @@ impl Display for ImportAlias { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Path { /// Type based path like `<T>::foo`. - /// Note that paths like `<Type as Trait>::foo` are desugard to `Trait::<Self=Type>::foo`. + /// Note that paths like `<Type as Trait>::foo` are desugared to `Trait::<Self=Type>::foo`. type_anchor: Option<Interned<TypeRef>>, mod_path: Interned<ModPath>, - /// Invariant: the same len as `self.mod_path.segments` - generic_args: Box<[Option<Interned<GenericArgs>>]>, + /// Invariant: the same len as `self.mod_path.segments` or `None` if all segments are `None`. + generic_args: Option<Box<[Option<Interned<GenericArgs>>]>>, } /// Generic arguments to a path segment (e.g. the `i32` in `Option<i32>`). This /// also includes bindings of associated types, like in `Iterator<Item = Foo>`. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct GenericArgs { - pub args: Vec<GenericArg>, + pub args: Box<[GenericArg]>, /// This specifies whether the args contain a Self type as the first /// element. This is the case for path segments like `<T as Trait>`, where /// `T` is actually a type parameter for the path `Trait` specifying the @@ -57,7 +57,7 @@ pub struct GenericArgs { /// is left out. pub has_self_type: bool, /// Associated type bindings like in `Iterator<Item = T>`. - pub bindings: Vec<AssociatedTypeBinding>, + pub bindings: Box<[AssociatedTypeBinding]>, /// Whether these generic args were desugared from `Trait(Arg) -> Output` /// parenthesis notation typically used for the `Fn` traits. pub desugared_from_fn: bool, @@ -77,7 +77,7 @@ pub struct AssociatedTypeBinding { /// Bounds for the associated type, like in `Iterator<Item: /// SomeOtherTrait>`. (This is the unstable `associated_type_bounds` /// feature.) - pub bounds: Vec<Interned<TypeBound>>, + pub bounds: Box<[Interned<TypeBound>]>, } /// A single generic argument. @@ -102,7 +102,7 @@ impl Path { ) -> Path { let generic_args = generic_args.into(); assert_eq!(path.len(), generic_args.len()); - Path { type_anchor: None, mod_path: Interned::new(path), generic_args } + Path { type_anchor: None, mod_path: Interned::new(path), generic_args: Some(generic_args) } } pub fn kind(&self) -> &PathKind { @@ -114,7 +114,14 @@ impl Path { } pub fn segments(&self) -> PathSegments<'_> { - PathSegments { segments: self.mod_path.segments(), generic_args: &self.generic_args } + let s = PathSegments { + segments: self.mod_path.segments(), + generic_args: self.generic_args.as_deref(), + }; + if let Some(generic_args) = s.generic_args { + assert_eq!(s.segments.len(), generic_args.len()); + } + s } pub fn mod_path(&self) -> &ModPath { @@ -131,13 +138,15 @@ impl Path { self.mod_path.kind, self.mod_path.segments()[..self.mod_path.segments().len() - 1].iter().cloned(), )), - generic_args: self.generic_args[..self.generic_args.len() - 1].to_vec().into(), + generic_args: self.generic_args.as_ref().map(|it| it[..it.len() - 1].to_vec().into()), }; Some(res) } pub fn is_self_type(&self) -> bool { - self.type_anchor.is_none() && *self.generic_args == [None] && self.mod_path.is_Self() + self.type_anchor.is_none() + && self.generic_args.as_deref().is_none() + && self.mod_path.is_Self() } } @@ -149,11 +158,11 @@ pub struct PathSegment<'a> { pub struct PathSegments<'a> { segments: &'a [Name], - generic_args: &'a [Option<Interned<GenericArgs>>], + generic_args: Option<&'a [Option<Interned<GenericArgs>>]>, } impl<'a> PathSegments<'a> { - pub const EMPTY: PathSegments<'static> = PathSegments { segments: &[], generic_args: &[] }; + pub const EMPTY: PathSegments<'static> = PathSegments { segments: &[], generic_args: None }; pub fn is_empty(&self) -> bool { self.len() == 0 } @@ -167,26 +176,29 @@ impl<'a> PathSegments<'a> { self.get(self.len().checked_sub(1)?) } pub fn get(&self, idx: usize) -> Option<PathSegment<'a>> { - assert_eq!(self.segments.len(), self.generic_args.len()); let res = PathSegment { name: self.segments.get(idx)?, - args_and_bindings: self.generic_args.get(idx).unwrap().as_ref().map(|it| &**it), + args_and_bindings: self.generic_args.and_then(|it| it.get(idx)?.as_deref()), }; Some(res) } pub fn skip(&self, len: usize) -> PathSegments<'a> { - assert_eq!(self.segments.len(), self.generic_args.len()); - PathSegments { segments: &self.segments[len..], generic_args: &self.generic_args[len..] } + PathSegments { + segments: &self.segments.get(len..).unwrap_or(&[]), + generic_args: self.generic_args.and_then(|it| it.get(len..)), + } } pub fn take(&self, len: usize) -> PathSegments<'a> { - assert_eq!(self.segments.len(), self.generic_args.len()); - PathSegments { segments: &self.segments[..len], generic_args: &self.generic_args[..len] } + PathSegments { + segments: &self.segments.get(..len).unwrap_or(&self.segments), + generic_args: self.generic_args.map(|it| it.get(..len).unwrap_or(it)), + } } pub fn iter(&self) -> impl Iterator<Item = PathSegment<'a>> { - self.segments.iter().zip(self.generic_args.iter()).map(|(name, args)| PathSegment { - name, - args_and_bindings: args.as_ref().map(|it| &**it), - }) + self.segments + .iter() + .zip(self.generic_args.into_iter().flatten().chain(iter::repeat(&None))) + .map(|(name, args)| PathSegment { name, args_and_bindings: args.as_deref() }) } } @@ -200,9 +212,9 @@ impl GenericArgs { pub(crate) fn empty() -> GenericArgs { GenericArgs { - args: Vec::new(), + args: Box::default(), has_self_type: false, - bindings: Vec::new(), + bindings: Box::default(), desugared_from_fn: false, } } @@ -213,7 +225,7 @@ impl From<Name> for Path { Path { type_anchor: None, mod_path: Interned::new(ModPath::from_segments(PathKind::Plain, iter::once(name))), - generic_args: Box::new([None]), + generic_args: None, } } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs index cfa3a6baa..c85a11db6 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs @@ -1,9 +1,12 @@ //! Transforms syntax into `Path` objects, ideally with accounting for hygiene -use crate::{intern::Interned, type_ref::ConstScalarOrPath}; +use std::iter; + +use crate::type_ref::ConstScalarOrPath; use either::Either; use hir_expand::name::{name, AsName}; +use intern::Interned; use syntax::ast::{self, AstNode, HasTypeBounds}; use super::AssociatedTypeBinding; @@ -44,8 +47,11 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option<Path ) }) .map(Interned::new); + if let Some(_) = args { + generic_args.resize(segments.len(), None); + generic_args.push(args); + } segments.push(name); - generic_args.push(args) } Either::Right(crate_id) => { kind = PathKind::DollarCrate(crate_id); @@ -55,7 +61,6 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option<Path } ast::PathSegmentKind::SelfTypeKw => { segments.push(name![Self]); - generic_args.push(None) } ast::PathSegmentKind::Type { type_ref, trait_ref } => { assert!(path.qualifier().is_none()); // this can only occur at the first segment @@ -76,18 +81,33 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option<Path kind = mod_path.kind; segments.extend(mod_path.segments().iter().cloned().rev()); - generic_args.extend(Vec::from(path_generic_args).into_iter().rev()); + if let Some(path_generic_args) = path_generic_args { + generic_args.resize(segments.len() - num_segments, None); + generic_args.extend(Vec::from(path_generic_args).into_iter().rev()); + } else { + generic_args.resize(segments.len(), None); + } + + let self_type = GenericArg::Type(self_type); // Insert the type reference (T in the above example) as Self parameter for the trait - let last_segment = - generic_args.iter_mut().rev().nth(num_segments.saturating_sub(1))?; - let mut args_inner = match last_segment { - Some(it) => it.as_ref().clone(), - None => GenericArgs::empty(), - }; - args_inner.has_self_type = true; - args_inner.args.insert(0, GenericArg::Type(self_type)); - *last_segment = Some(Interned::new(args_inner)); + let last_segment = generic_args.get_mut(segments.len() - num_segments)?; + *last_segment = Some(Interned::new(match last_segment.take() { + Some(it) => GenericArgs { + args: iter::once(self_type) + .chain(it.args.iter().cloned()) + .collect(), + + has_self_type: true, + bindings: it.bindings.clone(), + desugared_from_fn: it.desugared_from_fn, + }, + None => GenericArgs { + args: Box::new([self_type]), + has_self_type: true, + ..GenericArgs::empty() + }, + })); } } } @@ -114,7 +134,10 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option<Path }; } segments.reverse(); - generic_args.reverse(); + if !generic_args.is_empty() { + generic_args.resize(segments.len(), None); + generic_args.reverse(); + } if segments.is_empty() && kind == PathKind::Plain && type_anchor.is_none() { // plain empty paths don't exist, this means we got a single `self` segment as our path @@ -134,7 +157,11 @@ pub(super) fn lower_path(mut path: ast::Path, ctx: &LowerCtx<'_>) -> Option<Path } let mod_path = Interned::new(ModPath::from_segments(kind, segments)); - return Some(Path { type_anchor, mod_path, generic_args: generic_args.into() }); + return Some(Path { + type_anchor, + mod_path, + generic_args: if generic_args.is_empty() { None } else { Some(generic_args.into()) }, + }); fn qualifier(path: &ast::Path) -> Option<ast::Path> { if let Some(q) = path.qualifier() { @@ -173,7 +200,7 @@ pub(super) fn lower_generic_args( .map(|it| Interned::new(TypeBound::from_ast(lower_ctx, it))) .collect() } else { - Vec::new() + Box::default() }; bindings.push(AssociatedTypeBinding { name, args, type_ref, bounds }); } @@ -194,7 +221,12 @@ pub(super) fn lower_generic_args( if args.is_empty() && bindings.is_empty() { return None; } - Some(GenericArgs { args, has_self_type: false, bindings, desugared_from_fn: false }) + Some(GenericArgs { + args: args.into_boxed_slice(), + has_self_type: false, + bindings: bindings.into_boxed_slice(), + desugared_from_fn: false, + }) } /// Collect `GenericArgs` from the parts of a fn-like path, i.e. `Fn(X, Y) @@ -204,33 +236,30 @@ fn lower_generic_args_from_fn_path( params: Option<ast::ParamList>, ret_type: Option<ast::RetType>, ) -> Option<GenericArgs> { - let mut args = Vec::new(); - let mut bindings = Vec::new(); let params = params?; let mut param_types = Vec::new(); for param in params.params() { let type_ref = TypeRef::from_ast_opt(ctx, param.ty()); param_types.push(type_ref); } - let arg = GenericArg::Type(TypeRef::Tuple(param_types)); - args.push(arg); - if let Some(ret_type) = ret_type { + let args = Box::new([GenericArg::Type(TypeRef::Tuple(param_types))]); + let bindings = if let Some(ret_type) = ret_type { let type_ref = TypeRef::from_ast_opt(ctx, ret_type.ty()); - bindings.push(AssociatedTypeBinding { + Box::new([AssociatedTypeBinding { name: name![Output], args: None, type_ref: Some(type_ref), - bounds: Vec::new(), - }); + bounds: Box::default(), + }]) } else { // -> () let type_ref = TypeRef::Tuple(Vec::new()); - bindings.push(AssociatedTypeBinding { + Box::new([AssociatedTypeBinding { name: name![Output], args: None, type_ref: Some(type_ref), - bounds: Vec::new(), - }); - } + bounds: Box::default(), + }]) + }; Some(GenericArgs { args, has_self_type: false, bindings, desugared_from_fn: true }) } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/pretty.rs index befd0c5ff..2d45c8c8d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/pretty.rs @@ -3,10 +3,10 @@ use std::fmt::{self, Write}; use hir_expand::mod_path::PathKind; +use intern::Interned; use itertools::Itertools; use crate::{ - intern::Interned, path::{GenericArg, GenericArgs, Path}, type_ref::{Mutability, TraitBoundModifier, TypeBound, TypeRef}, }; @@ -71,7 +71,7 @@ pub(crate) fn print_generic_args(generics: &GenericArgs, buf: &mut dyn Write) -> first = false; print_generic_arg(arg, buf)?; } - for binding in &generics.bindings { + for binding in generics.bindings.iter() { if !first { write!(buf, ", ")?; } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs index 1ef7f9577..86958e3da 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs @@ -4,6 +4,7 @@ use std::{hash::BuildHasherDefault, sync::Arc}; use base_db::CrateId; use hir_expand::name::{name, Name}; use indexmap::IndexMap; +use intern::Interned; use rustc_hash::FxHashSet; use smallvec::{smallvec, SmallVec}; @@ -13,7 +14,6 @@ use crate::{ db::DefDatabase, expr::{ExprId, LabelId, PatId}, generics::{GenericParams, TypeOrConstParamData}, - intern::Interned, item_scope::{BuiltinShadowMode, BUILTIN_SCOPE}, nameres::DefMap, path::{ModPath, PathKind}, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/type_ref.rs b/src/tools/rust-analyzer/crates/hir-def/src/type_ref.rs index f8bb78ddc..9652b01b9 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/type_ref.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/type_ref.rs @@ -7,13 +7,13 @@ use hir_expand::{ name::{AsName, Name}, AstId, }; +use intern::Interned; use syntax::ast::{self, HasName}; use crate::{ body::LowerCtx, builtin_type::{BuiltinInt, BuiltinType, BuiltinUint}, expr::Literal, - intern::Interned, path::Path, }; @@ -240,7 +240,7 @@ impl TypeRef { TypeRef::DynTrait(type_bounds_from_ast(ctx, inner.type_bound_list())) } ast::Type::MacroType(mt) => match mt.macro_call() { - Some(mc) => ctx.ast_id(ctx.db, &mc).map(TypeRef::Macro).unwrap_or(TypeRef::Error), + Some(mc) => ctx.ast_id(&mc).map(TypeRef::Macro).unwrap_or(TypeRef::Error), None => TypeRef::Error, }, } @@ -292,7 +292,7 @@ impl TypeRef { } for segment in path.segments().iter() { if let Some(args_and_bindings) = segment.args_and_bindings { - for arg in &args_and_bindings.args { + for arg in args_and_bindings.args.iter() { match arg { crate::path::GenericArg::Type(type_ref) => { go(type_ref, f); @@ -301,11 +301,11 @@ impl TypeRef { | crate::path::GenericArg::Lifetime(_) => {} } } - for binding in &args_and_bindings.bindings { + for binding in args_and_bindings.bindings.iter() { if let Some(type_ref) = &binding.type_ref { go(type_ref, f); } - for bound in &binding.bounds { + for bound in binding.bounds.iter() { match bound.as_ref() { TypeBound::Path(path, _) | TypeBound::ForLifetime(_, path) => { go_path(path, f) diff --git a/src/tools/rust-analyzer/crates/hir-expand/Cargo.toml b/src/tools/rust-analyzer/crates/hir-expand/Cargo.toml index 77eb1fd45..5c684be03 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/Cargo.toml +++ b/src/tools/rust-analyzer/crates/hir-expand/Cargo.toml @@ -2,9 +2,11 @@ name = "hir-expand" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -19,16 +21,18 @@ itertools = "0.10.5" hashbrown = { version = "0.12.1", features = [ "inline-more", ], default-features = false } -smallvec = { version = "1.10.0", features = ["const_new"] } +smallvec.workspace = true -stdx = { path = "../stdx", version = "0.0.0" } -base-db = { path = "../base-db", version = "0.0.0" } -cfg = { path = "../cfg", version = "0.0.0" } -syntax = { path = "../syntax", version = "0.0.0" } -profile = { path = "../profile", version = "0.0.0" } -tt = { path = "../tt", version = "0.0.0" } -mbe = { path = "../mbe", version = "0.0.0" } -limit = { path = "../limit", version = "0.0.0" } +# local deps +stdx.workspace = true +intern.workspace = true +base-db.workspace = true +cfg.workspace = true +syntax.workspace = true +profile.workspace = true +tt.workspace = true +mbe.workspace = true +limit.workspace = true [dev-dependencies] expect-test = "1.4.0" diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs new file mode 100644 index 000000000..5c04f8e8b --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs @@ -0,0 +1,349 @@ +//! A higher level attributes based on TokenTree, with also some shortcuts. +use std::{fmt, ops, sync::Arc}; + +use base_db::CrateId; +use cfg::CfgExpr; +use either::Either; +use intern::Interned; +use mbe::{syntax_node_to_token_tree, DelimiterKind, Punct}; +use smallvec::{smallvec, SmallVec}; +use syntax::{ast, match_ast, AstNode, SmolStr, SyntaxNode}; + +use crate::{ + db::AstDatabase, + hygiene::Hygiene, + mod_path::{ModPath, PathKind}, + name::AsName, + tt::{self, Subtree}, + InFile, +}; + +/// Syntactical attributes, without filtering of `cfg_attr`s. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct RawAttrs { + entries: Option<Arc<[Attr]>>, +} + +impl ops::Deref for RawAttrs { + type Target = [Attr]; + + fn deref(&self) -> &[Attr] { + match &self.entries { + Some(it) => &*it, + None => &[], + } + } +} + +impl RawAttrs { + pub const EMPTY: Self = Self { entries: None }; + + pub fn new(db: &dyn AstDatabase, owner: &dyn ast::HasAttrs, hygiene: &Hygiene) -> Self { + let entries = collect_attrs(owner) + .filter_map(|(id, attr)| match attr { + Either::Left(attr) => { + attr.meta().and_then(|meta| Attr::from_src(db, meta, hygiene, id)) + } + Either::Right(comment) => comment.doc_comment().map(|doc| Attr { + id, + input: Some(Interned::new(AttrInput::Literal(SmolStr::new(doc)))), + path: Interned::new(ModPath::from(crate::name!(doc))), + }), + }) + .collect::<Arc<_>>(); + + Self { entries: if entries.is_empty() { None } else { Some(entries) } } + } + + pub fn from_attrs_owner(db: &dyn AstDatabase, owner: InFile<&dyn ast::HasAttrs>) -> Self { + let hygiene = Hygiene::new(db, owner.file_id); + Self::new(db, owner.value, &hygiene) + } + + pub fn merge(&self, other: Self) -> Self { + match (&self.entries, other.entries) { + (None, None) => Self::EMPTY, + (None, entries @ Some(_)) => Self { entries }, + (Some(entries), None) => Self { entries: Some(entries.clone()) }, + (Some(a), Some(b)) => { + let last_ast_index = a.last().map_or(0, |it| it.id.ast_index() + 1) as u32; + Self { + entries: Some( + a.iter() + .cloned() + .chain(b.iter().map(|it| { + let mut it = it.clone(); + it.id.id = it.id.ast_index() as u32 + last_ast_index + | (it.id.cfg_attr_index().unwrap_or(0) as u32) + << AttrId::AST_INDEX_BITS; + it + })) + .collect(), + ), + } + } + } + } + + /// Processes `cfg_attr`s, returning the resulting semantic `Attrs`. + // FIXME: This should return a different type + pub fn filter(self, db: &dyn AstDatabase, krate: CrateId) -> RawAttrs { + let has_cfg_attrs = self + .iter() + .any(|attr| attr.path.as_ident().map_or(false, |name| *name == crate::name![cfg_attr])); + if !has_cfg_attrs { + return self; + } + + let crate_graph = db.crate_graph(); + let new_attrs = self + .iter() + .flat_map(|attr| -> SmallVec<[_; 1]> { + let is_cfg_attr = + attr.path.as_ident().map_or(false, |name| *name == crate::name![cfg_attr]); + if !is_cfg_attr { + return smallvec![attr.clone()]; + } + + let subtree = match attr.token_tree_value() { + Some(it) => it, + _ => return smallvec![attr.clone()], + }; + + let (cfg, parts) = match parse_cfg_attr_input(subtree) { + Some(it) => it, + None => return smallvec![attr.clone()], + }; + let index = attr.id; + let attrs = + parts.enumerate().take(1 << AttrId::CFG_ATTR_BITS).filter_map(|(idx, attr)| { + let tree = Subtree { + delimiter: tt::Delimiter::unspecified(), + token_trees: attr.to_vec(), + }; + // FIXME hygiene + let hygiene = Hygiene::new_unhygienic(); + Attr::from_tt(db, &tree, &hygiene, index.with_cfg_attr(idx)) + }); + + let cfg_options = &crate_graph[krate].cfg_options; + let cfg = Subtree { delimiter: subtree.delimiter, token_trees: cfg.to_vec() }; + let cfg = CfgExpr::parse(&cfg); + if cfg_options.check(&cfg) == Some(false) { + smallvec![] + } else { + cov_mark::hit!(cfg_attr_active); + + attrs.collect() + } + }) + .collect(); + + RawAttrs { entries: Some(new_attrs) } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct AttrId { + id: u32, +} + +// FIXME: This only handles a single level of cfg_attr nesting +// that is `#[cfg_attr(all(), cfg_attr(all(), cfg(any())))]` breaks again +impl AttrId { + const CFG_ATTR_BITS: usize = 7; + const AST_INDEX_MASK: usize = 0x00FF_FFFF; + const AST_INDEX_BITS: usize = Self::AST_INDEX_MASK.count_ones() as usize; + const CFG_ATTR_SET_BITS: u32 = 1 << 31; + + pub fn ast_index(&self) -> usize { + self.id as usize & Self::AST_INDEX_MASK + } + + pub fn cfg_attr_index(&self) -> Option<usize> { + if self.id & Self::CFG_ATTR_SET_BITS == 0 { + None + } else { + Some(self.id as usize >> Self::AST_INDEX_BITS) + } + } + + pub fn with_cfg_attr(self, idx: usize) -> AttrId { + AttrId { id: self.id | (idx as u32) << Self::AST_INDEX_BITS | Self::CFG_ATTR_SET_BITS } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Attr { + pub id: AttrId, + pub path: Interned<ModPath>, + pub input: Option<Interned<AttrInput>>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum AttrInput { + /// `#[attr = "string"]` + Literal(SmolStr), + /// `#[attr(subtree)]` + TokenTree(tt::Subtree, mbe::TokenMap), +} + +impl fmt::Display for AttrInput { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AttrInput::Literal(lit) => write!(f, " = \"{}\"", lit.escape_debug()), + AttrInput::TokenTree(subtree, _) => subtree.fmt(f), + } + } +} + +impl Attr { + fn from_src( + db: &dyn AstDatabase, + ast: ast::Meta, + hygiene: &Hygiene, + id: AttrId, + ) -> Option<Attr> { + let path = Interned::new(ModPath::from_src(db, ast.path()?, hygiene)?); + let input = if let Some(ast::Expr::Literal(lit)) = ast.expr() { + let value = match lit.kind() { + ast::LiteralKind::String(string) => string.value()?.into(), + _ => lit.syntax().first_token()?.text().trim_matches('"').into(), + }; + Some(Interned::new(AttrInput::Literal(value))) + } else if let Some(tt) = ast.token_tree() { + let (tree, map) = syntax_node_to_token_tree(tt.syntax()); + Some(Interned::new(AttrInput::TokenTree(tree, map))) + } else { + None + }; + Some(Attr { id, path, input }) + } + + fn from_tt( + db: &dyn AstDatabase, + tt: &tt::Subtree, + hygiene: &Hygiene, + id: AttrId, + ) -> Option<Attr> { + let (parse, _) = mbe::token_tree_to_syntax_node(tt, mbe::TopEntryPoint::MetaItem); + let ast = ast::Meta::cast(parse.syntax_node())?; + + Self::from_src(db, ast, hygiene, id) + } + + pub fn path(&self) -> &ModPath { + &self.path + } +} + +impl Attr { + /// #[path = "string"] + pub fn string_value(&self) -> Option<&SmolStr> { + match self.input.as_deref()? { + AttrInput::Literal(it) => Some(it), + _ => None, + } + } + + /// #[path(ident)] + pub fn single_ident_value(&self) -> Option<&tt::Ident> { + match self.input.as_deref()? { + AttrInput::TokenTree(subtree, _) => match &*subtree.token_trees { + [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] => Some(ident), + _ => None, + }, + _ => None, + } + } + + /// #[path TokenTree] + pub fn token_tree_value(&self) -> Option<&Subtree> { + match self.input.as_deref()? { + AttrInput::TokenTree(subtree, _) => Some(subtree), + _ => None, + } + } + + /// Parses this attribute as a token tree consisting of comma separated paths. + pub fn parse_path_comma_token_tree(&self) -> Option<impl Iterator<Item = ModPath> + '_> { + let args = self.token_tree_value()?; + + if args.delimiter.kind != DelimiterKind::Parenthesis { + return None; + } + let paths = args + .token_trees + .split(|tt| matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(Punct { char: ',', .. })))) + .filter_map(|tts| { + if tts.is_empty() { + return None; + } + let segments = tts.iter().filter_map(|tt| match tt { + tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => Some(id.as_name()), + _ => None, + }); + Some(ModPath::from_segments(PathKind::Plain, segments)) + }); + + Some(paths) + } +} + +pub fn collect_attrs( + owner: &dyn ast::HasAttrs, +) -> impl Iterator<Item = (AttrId, Either<ast::Attr, ast::Comment>)> { + let inner_attrs = inner_attributes(owner.syntax()).into_iter().flatten(); + let outer_attrs = + ast::AttrDocCommentIter::from_syntax_node(owner.syntax()).filter(|el| match el { + Either::Left(attr) => attr.kind().is_outer(), + Either::Right(comment) => comment.is_outer(), + }); + outer_attrs.chain(inner_attrs).enumerate().map(|(id, attr)| (AttrId { id: id as u32 }, attr)) +} + +fn inner_attributes( + syntax: &SyntaxNode, +) -> Option<impl Iterator<Item = Either<ast::Attr, ast::Comment>>> { + let node = match_ast! { + match syntax { + ast::SourceFile(_) => syntax.clone(), + ast::ExternBlock(it) => it.extern_item_list()?.syntax().clone(), + ast::Fn(it) => it.body()?.stmt_list()?.syntax().clone(), + ast::Impl(it) => it.assoc_item_list()?.syntax().clone(), + ast::Module(it) => it.item_list()?.syntax().clone(), + ast::BlockExpr(it) => { + use syntax::SyntaxKind::{BLOCK_EXPR , EXPR_STMT}; + // Block expressions accept outer and inner attributes, but only when they are the outer + // expression of an expression statement or the final expression of another block expression. + let may_carry_attributes = matches!( + it.syntax().parent().map(|it| it.kind()), + Some(BLOCK_EXPR | EXPR_STMT) + ); + if !may_carry_attributes { + return None + } + syntax.clone() + }, + _ => return None, + } + }; + + let attrs = ast::AttrDocCommentIter::from_syntax_node(&node).filter(|el| match el { + Either::Left(attr) => attr.kind().is_inner(), + Either::Right(comment) => comment.is_inner(), + }); + Some(attrs) +} + +// Input subtree is: `(cfg, $(attr),+)` +// Split it up into a `cfg` subtree and the `attr` subtrees. +pub fn parse_cfg_attr_input( + subtree: &Subtree, +) -> Option<(&[tt::TokenTree], impl Iterator<Item = &[tt::TokenTree]>)> { + let mut parts = subtree + .token_trees + .split(|tt| matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(Punct { char: ',', .. })))); + let cfg = parts.next()?; + Some((cfg, parts.filter(|it| !it.is_empty()))) +} diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs index 58d192f9f..906ca991d 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs @@ -1,6 +1,6 @@ //! Builtin attributes. -use crate::{db::AstDatabase, name, ExpandResult, MacroCallId, MacroCallKind}; +use crate::{db::AstDatabase, name, tt, ExpandResult, MacroCallId, MacroCallKind}; macro_rules! register_builtin { ( $(($name:ident, $variant:ident) => $expand:ident),* ) => { @@ -97,7 +97,7 @@ fn derive_attr_expand( let loc = db.lookup_intern_macro_call(id); let derives = match &loc.kind { MacroCallKind::Attr { attr_args, is_derive: true, .. } => &attr_args.0, - _ => return ExpandResult::ok(Default::default()), + _ => return ExpandResult::ok(tt::Subtree::empty()), }; pseudo_derive_attr_expansion(tt, derives) } @@ -110,7 +110,7 @@ pub fn pseudo_derive_attr_expansion( tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char, spacing: tt::Spacing::Alone, - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), })) }; diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs index 8966047c9..060a68054 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs @@ -3,11 +3,11 @@ use base_db::{CrateOrigin, LangCrateOrigin}; use tracing::debug; +use crate::tt::{self, TokenId}; use syntax::{ ast::{self, AstNode, HasGenericParams, HasModuleItem, HasName}, match_ast, }; -use tt::TokenId; use crate::{db::AstDatabase, name, quote, ExpandError, ExpandResult, MacroCallId}; @@ -92,7 +92,7 @@ fn parse_adt(tt: &tt::Subtree) -> Result<BasicAdtInfo, ExpandError> { })?; let name_token_id = token_map.token_by_range(name.syntax().text_range()).unwrap_or_else(TokenId::unspecified); - let name_token = tt::Ident { id: name_token_id, text: name.text().into() }; + let name_token = tt::Ident { span: name_token_id, text: name.text().into() }; let param_types = params .into_iter() .flat_map(|param_list| param_list.type_or_const_params()) @@ -101,7 +101,7 @@ fn parse_adt(tt: &tt::Subtree) -> Result<BasicAdtInfo, ExpandError> { let ty = param .ty() .map(|ty| mbe::syntax_node_to_token_tree(ty.syntax()).0) - .unwrap_or_default(); + .unwrap_or_else(tt::Subtree::empty); Some(ty) } else { None @@ -114,7 +114,7 @@ fn parse_adt(tt: &tt::Subtree) -> Result<BasicAdtInfo, ExpandError> { fn expand_simple_derive(tt: &tt::Subtree, trait_path: tt::Subtree) -> ExpandResult<tt::Subtree> { let info = match parse_adt(tt) { Ok(info) => info, - Err(e) => return ExpandResult::only_err(e), + Err(e) => return ExpandResult::with_err(tt::Subtree::empty(), e), }; let (params, args): (Vec<_>, Vec<_>) = info .param_types @@ -122,7 +122,7 @@ fn expand_simple_derive(tt: &tt::Subtree, trait_path: tt::Subtree) -> ExpandResu .enumerate() .map(|(idx, param_ty)| { let ident = tt::Leaf::Ident(tt::Ident { - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), text: format!("T{idx}").into(), }); let ident_ = ident.clone(); diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs index 5522bdf3b..9f3fa73d4 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs @@ -9,7 +9,9 @@ use syntax::{ SmolStr, }; -use crate::{db::AstDatabase, name, quote, ExpandError, ExpandResult, MacroCallId, MacroCallLoc}; +use crate::{ + db::AstDatabase, name, quote, tt, ExpandError, ExpandResult, MacroCallId, MacroCallLoc, +}; macro_rules! register_builtin { ( LAZY: $(($name:ident, $kind: ident) => $expand:ident),* , EAGER: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => { @@ -61,7 +63,7 @@ macro_rules! register_builtin { }; } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct ExpandedEager { pub(crate) subtree: tt::Subtree, /// The included file ID of the include macro. @@ -116,7 +118,7 @@ register_builtin! { } const DOLLAR_CRATE: tt::Ident = - tt::Ident { text: SmolStr::new_inline("$crate"), id: tt::TokenId::unspecified() }; + tt::Ident { text: SmolStr::new_inline("$crate"), span: tt::TokenId::unspecified() }; fn module_path_expand( _db: &dyn AstDatabase, @@ -162,7 +164,7 @@ fn stringify_expand( _id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { - let pretty = tt::pretty(&tt.token_trees); + let pretty = ::tt::pretty(&tt.token_trees); let expanded = quote! { #pretty @@ -194,11 +196,11 @@ fn assert_expand( let expanded = match &*args { [cond, panic_args @ ..] => { let comma = tt::Subtree { - delimiter: None, + delimiter: tt::Delimiter::unspecified(), token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', spacing: tt::Spacing::Alone, - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), }))], }; let cond = cond.clone(); @@ -247,7 +249,10 @@ fn format_args_expand( let mut args = parse_exprs_with_sep(tt, ','); if args.is_empty() { - return ExpandResult::only_err(mbe::ExpandError::NoMatchingRule.into()); + return ExpandResult::with_err( + tt::Subtree::empty(), + mbe::ExpandError::NoMatchingRule.into(), + ); } for arg in &mut args { // Remove `key =`. @@ -282,7 +287,7 @@ fn asm_expand( for tt in tt.token_trees.chunks(2) { match tt { [tt::TokenTree::Leaf(tt::Leaf::Literal(lit))] - | [tt::TokenTree::Leaf(tt::Leaf::Literal(lit)), tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', id: _, spacing: _ }))] => + | [tt::TokenTree::Leaf(tt::Leaf::Literal(lit)), tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', span: _, spacing: _ }))] => { let krate = DOLLAR_CRATE.clone(); literals.push(quote!(#krate::format_args!(#lit);)); @@ -400,7 +405,7 @@ fn concat_expand( // FIXME: hack on top of a hack: `$e:expr` captures get surrounded in parentheses // to ensure the right parsing order, so skip the parentheses here. Ideally we'd // implement rustc's model. cc https://github.com/rust-lang/rust-analyzer/pull/10623 - if let tt::TokenTree::Subtree(tt::Subtree { delimiter: Some(delim), token_trees }) = t { + if let tt::TokenTree::Subtree(tt::Subtree { delimiter: delim, token_trees }) = t { if let [tt] = &**token_trees { if delim.kind == tt::DelimiterKind::Parenthesis { t = tt; @@ -459,9 +464,7 @@ fn concat_bytes_expand( } } tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), - tt::TokenTree::Subtree(tree) - if tree.delimiter_kind() == Some(tt::DelimiterKind::Bracket) => - { + tt::TokenTree::Subtree(tree) if tree.delimiter.kind == tt::DelimiterKind::Bracket => { if let Err(e) = concat_bytes_expand_subtree(tree, &mut bytes) { err.get_or_insert(e); break; @@ -473,7 +476,7 @@ fn concat_bytes_expand( } } } - let ident = tt::Ident { text: bytes.join(", ").into(), id: tt::TokenId::unspecified() }; + let ident = tt::Ident { text: bytes.join(", ").into(), span: tt::TokenId::unspecified() }; ExpandResult { value: ExpandedEager::new(quote!([#ident])), err } } @@ -521,7 +524,7 @@ fn concat_idents_expand( } } } - let ident = tt::Ident { text: ident.into(), id: tt::TokenId::unspecified() }; + let ident = tt::Ident { text: ident.into(), span: tt::TokenId::unspecified() }; ExpandResult { value: ExpandedEager::new(quote!(#ident)), err } } @@ -572,7 +575,10 @@ fn include_expand( Ok((subtree, file_id)) => { ExpandResult::ok(ExpandedEager { subtree, included_file: Some(file_id) }) } - Err(e) => ExpandResult::only_err(e), + Err(e) => ExpandResult::with_err( + ExpandedEager { subtree: tt::Subtree::empty(), included_file: None }, + e, + ), } } @@ -582,15 +588,18 @@ fn include_bytes_expand( tt: &tt::Subtree, ) -> ExpandResult<ExpandedEager> { if let Err(e) = parse_string(tt) { - return ExpandResult::only_err(e); + return ExpandResult::with_err( + ExpandedEager { subtree: tt::Subtree::empty(), included_file: None }, + e, + ); } // FIXME: actually read the file here if the user asked for macro expansion let res = tt::Subtree { - delimiter: None, + delimiter: tt::Delimiter::unspecified(), token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { text: r#"b"""#.into(), - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), }))], }; ExpandResult::ok(ExpandedEager::new(res)) @@ -603,7 +612,12 @@ fn include_str_expand( ) -> ExpandResult<ExpandedEager> { let path = match parse_string(tt) { Ok(it) => it, - Err(e) => return ExpandResult::only_err(e), + Err(e) => { + return ExpandResult::with_err( + ExpandedEager { subtree: tt::Subtree::empty(), included_file: None }, + e, + ) + } }; // FIXME: we're not able to read excluded files (which is most of them because @@ -635,7 +649,12 @@ fn env_expand( ) -> ExpandResult<ExpandedEager> { let key = match parse_string(tt) { Ok(it) => it, - Err(e) => return ExpandResult::only_err(e), + Err(e) => { + return ExpandResult::with_err( + ExpandedEager { subtree: tt::Subtree::empty(), included_file: None }, + e, + ) + } }; let mut err = None; @@ -666,7 +685,12 @@ fn option_env_expand( ) -> ExpandResult<ExpandedEager> { let key = match parse_string(tt) { Ok(it) => it, - Err(e) => return ExpandResult::only_err(e), + Err(e) => { + return ExpandResult::with_err( + ExpandedEager { subtree: tt::Subtree::empty(), included_file: None }, + e, + ) + } }; let expanded = match get_env_inner(db, arg_id, &key) { diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs index b28e60187..76016274f 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs @@ -14,7 +14,7 @@ use syntax::{ use crate::{ ast_id_map::AstIdMap, builtin_attr_macro::pseudo_derive_attr_expansion, fixup, - hygiene::HygieneFrame, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, + hygiene::HygieneFrame, tt, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, ExpandError, ExpandResult, ExpandTo, HirFileId, HirFileIdRepr, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander, }; @@ -25,7 +25,7 @@ use crate::{ /// an error will be emitted. /// /// Actual max for `analysis-stats .` at some point: 30672. -static TOKEN_LIMIT: Limit = Limit::new(524_288); +static TOKEN_LIMIT: Limit = Limit::new(1_048_576); #[derive(Debug, Clone, Eq, PartialEq)] pub enum TokenExpander { @@ -168,12 +168,14 @@ pub fn expand_speculative( // Attributes may have an input token tree, build the subtree and map for this as well // then try finding a token id for our token if it is inside this input subtree. let item = ast::Item::cast(speculative_args.clone())?; - item.doc_comments_and_attrs().nth(invoc_attr_index as usize).and_then(Either::left) + item.doc_comments_and_attrs() + .nth(invoc_attr_index.ast_index()) + .and_then(Either::left) }?; match attr.token_tree() { Some(token_tree) => { let (mut tree, map) = syntax_node_to_token_tree(attr.token_tree()?.syntax()); - tree.delimiter = None; + tree.delimiter = tt::Delimiter::unspecified(); let shift = mbe::Shift::new(&tt); shift.shift_all(&mut tree); @@ -208,7 +210,7 @@ pub fn expand_speculative( // Otherwise the expand query will fetch the non speculative attribute args and pass those instead. let mut speculative_expansion = match loc.def.kind { MacroDefKind::ProcMacro(expander, ..) => { - tt.delimiter = None; + tt.delimiter = tt::Delimiter::unspecified(); expander.expand(db, loc.krate, &tt, attr_arg.as_ref()) } MacroDefKind::BuiltInAttr(BuiltinAttrExpander::Derive, _) => { @@ -314,13 +316,13 @@ fn macro_arg( if loc.def.is_proc_macro() { // proc macros expect their inputs without parentheses, MBEs expect it with them included - tt.delimiter = None; + tt.delimiter = tt::Delimiter::unspecified(); } - Some(Arc::new((tt, tmap, fixups.undo_info))) } fn censor_for_macro_input(loc: &MacroCallLoc, node: &SyntaxNode) -> FxHashSet<SyntaxNode> { + // FIXME: handle `cfg_attr` (|| { let censor = match loc.kind { MacroCallKind::FnLike { .. } => return None, @@ -328,7 +330,7 @@ fn censor_for_macro_input(loc: &MacroCallLoc, node: &SyntaxNode) -> FxHashSet<Sy cov_mark::hit!(derive_censoring); ast::Item::cast(node.clone())? .attrs() - .take(derive_attr_index as usize + 1) + .take(derive_attr_index.ast_index() + 1) // FIXME, this resolution should not be done syntactically // derive is a proper macro now, no longer builtin // But we do not have resolution at this stage, this means @@ -343,7 +345,7 @@ fn censor_for_macro_input(loc: &MacroCallLoc, node: &SyntaxNode) -> FxHashSet<Sy cov_mark::hit!(attribute_macro_attr_censoring); ast::Item::cast(node.clone())? .doc_comments_and_attrs() - .nth(invoc_attr_index as usize) + .nth(invoc_attr_index.ast_index()) .and_then(Either::left) .map(|attr| attr.syntax().clone()) .into_iter() @@ -476,7 +478,10 @@ fn expand_proc_macro(db: &dyn AstDatabase, id: MacroCallId) -> ExpandResult<tt:: let macro_arg = match db.macro_arg(id) { Some(it) => it, None => { - return ExpandResult::only_err(ExpandError::Other("No arguments for proc-macro".into())) + return ExpandResult::with_err( + tt::Subtree::empty(), + ExpandError::Other("No arguments for proc-macro".into()), + ) } }; diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs b/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs index a1474c44e..dfab7ec92 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs @@ -108,7 +108,7 @@ pub fn expand_eager_macro( .value .token_tree() .map(|tt| mbe::syntax_node_to_token_tree(tt.syntax()).0) - .unwrap_or_default(); + .unwrap_or_else(tt::Subtree::empty); let ast_map = db.ast_id_map(macro_call.file_id); let call_id = InFile::new(macro_call.file_id, ast_map.ast_id(¯o_call.value)); @@ -165,9 +165,9 @@ pub fn expand_eager_macro( } } -fn to_subtree(node: &SyntaxNode) -> tt::Subtree { +fn to_subtree(node: &SyntaxNode) -> crate::tt::Subtree { let mut subtree = mbe::syntax_node_to_token_tree(node).0; - subtree.delimiter = None; + subtree.delimiter = crate::tt::Delimiter::unspecified(); subtree } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs index 75d364d5f..c811d1c66 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs @@ -9,7 +9,7 @@ use syntax::{ ast::{self, AstNode, HasLoopBody}, match_ast, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, }; -use tt::Subtree; +use tt::token_id::Subtree; /// The result of calculating fixes for a syntax node -- a bunch of changes /// (appending to and replacing nodes), the information that is needed to @@ -297,9 +297,11 @@ pub(crate) fn reverse_fixups( tt.token_trees = tts .into_iter() .filter(|tt| match tt { - tt::TokenTree::Leaf(leaf) => token_map.synthetic_token_id(leaf.id()) != Some(EMPTY_ID), + tt::TokenTree::Leaf(leaf) => { + token_map.synthetic_token_id(*leaf.span()) != Some(EMPTY_ID) + } tt::TokenTree::Subtree(st) => { - st.delimiter.map_or(true, |d| token_map.synthetic_token_id(d.id) != Some(EMPTY_ID)) + token_map.synthetic_token_id(st.delimiter.open) != Some(EMPTY_ID) } }) .flat_map(|tt| match tt { @@ -308,9 +310,9 @@ pub(crate) fn reverse_fixups( SmallVec::from_const([tt.into()]) } tt::TokenTree::Leaf(leaf) => { - if let Some(id) = token_map.synthetic_token_id(leaf.id()) { + if let Some(id) = token_map.synthetic_token_id(*leaf.span()) { let original = undo_info.original[id.0 as usize].clone(); - if original.delimiter.is_none() { + if original.delimiter.kind == tt::DelimiterKind::Invisible { original.token_trees.into() } else { SmallVec::from_const([original.into()]) @@ -327,6 +329,8 @@ pub(crate) fn reverse_fixups( mod tests { use expect_test::{expect, Expect}; + use crate::tt; + use super::reverse_fixups; // The following three functions are only meant to check partial structural equivalence of @@ -341,7 +345,7 @@ mod tests { } fn check_subtree_eq(a: &tt::Subtree, b: &tt::Subtree) -> bool { - a.delimiter.map(|it| it.kind) == b.delimiter.map(|it| it.kind) + a.delimiter.kind == b.delimiter.kind && a.token_trees.len() == b.token_trees.len() && a.token_trees.iter().zip(&b.token_trees).all(|(a, b)| check_tt_eq(a, b)) } @@ -386,7 +390,7 @@ mod tests { let (original_as_tt, _) = mbe::syntax_node_to_token_tree(&parsed.syntax_node()); assert!( check_subtree_eq(&tt, &original_as_tt), - "different token tree: {tt:?}, {original_as_tt:?}" + "different token tree: {tt:?},\n{original_as_tt:?}" ); } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs b/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs index df1e20256..2300ee9d0 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs @@ -128,7 +128,7 @@ struct HygieneInfo { attr_input_or_mac_def_start: Option<InFile<TextSize>>, macro_def: Arc<TokenExpander>, - macro_arg: Arc<(tt::Subtree, mbe::TokenMap, fixup::SyntaxFixupUndoInfo)>, + macro_arg: Arc<(crate::tt::Subtree, mbe::TokenMap, fixup::SyntaxFixupUndoInfo)>, macro_arg_shift: mbe::Shift, exp_map: Arc<mbe::TokenMap>, } @@ -191,7 +191,7 @@ fn make_hygiene_info( let tt = ast_id .to_node(db) .doc_comments_and_attrs() - .nth(invoc_attr_index as usize) + .nth(invoc_attr_index.ast_index()) .and_then(Either::left)? .token_tree()?; Some(InFile::new(ast_id.file_id, tt)) diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs index bc5f9f3b8..a52716cc0 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs @@ -17,10 +17,13 @@ pub mod proc_macro; pub mod quote; pub mod eager; pub mod mod_path; +pub mod attrs; mod fixup; pub use mbe::{Origin, ValueResult}; +use ::tt::token_id as tt; + use std::{fmt, hash::Hash, iter, sync::Arc}; use base_db::{ @@ -37,6 +40,7 @@ use syntax::{ use crate::{ ast_id_map::FileAstId, + attrs::AttrId, builtin_attr_macro::BuiltinAttrExpander, builtin_derive_macro::BuiltinDeriveExpander, builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander}, @@ -51,6 +55,7 @@ pub type ExpandResult<T> = ValueResult<T, ExpandError>; pub enum ExpandError { UnresolvedProcMacro(CrateId), Mbe(mbe::ExpandError), + RecursionOverflowPosioned, Other(Box<str>), } @@ -65,6 +70,9 @@ impl fmt::Display for ExpandError { match self { ExpandError::UnresolvedProcMacro(_) => f.write_str("unresolved proc-macro"), ExpandError::Mbe(it) => it.fmt(f), + ExpandError::RecursionOverflowPosioned => { + f.write_str("overflow expanding the original macro") + } ExpandError::Other(it) => f.write_str(it), } } @@ -114,6 +122,7 @@ pub struct MacroDefId { pub krate: CrateId, pub kind: MacroDefKind, pub local_inner: bool, + pub allow_internal_unsafe: bool, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -145,7 +154,7 @@ pub enum MacroCallKind { /// /// Outer attributes are counted first, then inner attributes. This does not support /// out-of-line modules, which may have attributes spread across 2 files! - derive_attr_index: u32, + derive_attr_index: AttrId, /// Index of the derive macro in the derive attribute derive_index: u32, }, @@ -156,7 +165,7 @@ pub enum MacroCallKind { /// /// Outer attributes are counted first, then inner attributes. This does not support /// out-of-line modules, which may have attributes spread across 2 files! - invoc_attr_index: u32, + invoc_attr_index: AttrId, /// Whether this attribute is the `#[derive]` attribute. is_derive: bool, }, @@ -261,10 +270,11 @@ impl HirFileId { }); let attr_input_or_mac_def = def.or_else(|| match loc.kind { MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => { + // FIXME: handle `cfg_attr` let tt = ast_id .to_node(db) .doc_comments_and_attrs() - .nth(invoc_attr_index as usize) + .nth(invoc_attr_index.ast_index()) .and_then(Either::left)? .token_tree()?; Some(InFile::new(ast_id.file_id, tt)) @@ -353,6 +363,14 @@ impl HirFileId { } } + #[inline] + pub fn file_id(self) -> Option<FileId> { + match self.0 & Self::MACRO_FILE_TAG_MASK { + 0 => Some(FileId(self.0)), + _ => None, + } + } + fn repr(self) -> HirFileIdRepr { match self.0 & Self::MACRO_FILE_TAG_MASK { 0 => HirFileIdRepr::FileId(FileId(self.0)), @@ -397,8 +415,7 @@ impl MacroDefId { } } -// FIXME: attribute indices do not account for `cfg_attr`, which means that we'll strip the whole -// `cfg_attr` instead of just one of the attributes it expands to +// FIXME: attribute indices do not account for nested `cfg_attr` impl MacroCallKind { /// Returns the file containing the macro invocation. @@ -419,7 +436,7 @@ impl MacroCallKind { // FIXME: handle `cfg_attr` ast_id.with_value(ast_id.to_node(db)).map(|it| { it.doc_comments_and_attrs() - .nth(*derive_attr_index as usize) + .nth(derive_attr_index.ast_index()) .and_then(|it| match it { Either::Left(attr) => Some(attr.syntax().clone()), Either::Right(_) => None, @@ -431,7 +448,7 @@ impl MacroCallKind { // FIXME: handle `cfg_attr` ast_id.with_value(ast_id.to_node(db)).map(|it| { it.doc_comments_and_attrs() - .nth(*invoc_attr_index as usize) + .nth(invoc_attr_index.ast_index()) .and_then(|it| match it { Either::Left(attr) => Some(attr.syntax().clone()), Either::Right(_) => None, @@ -488,19 +505,21 @@ impl MacroCallKind { MacroCallKind::FnLike { ast_id, .. } => ast_id.to_node(db).syntax().text_range(), MacroCallKind::Derive { ast_id, derive_attr_index, .. } => { // FIXME: should be the range of the macro name, not the whole derive + // FIXME: handle `cfg_attr` ast_id .to_node(db) .doc_comments_and_attrs() - .nth(derive_attr_index as usize) + .nth(derive_attr_index.ast_index()) .expect("missing derive") .expect_left("derive is a doc comment?") .syntax() .text_range() } + // FIXME: handle `cfg_attr` MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => ast_id .to_node(db) .doc_comments_and_attrs() - .nth(invoc_attr_index as usize) + .nth(invoc_attr_index.ast_index()) .expect("missing attribute") .expect_left("attribute macro is a doc comment?") .syntax() @@ -592,9 +611,10 @@ impl ExpansionInfo { let token_range = token.value.text_range(); match &loc.kind { MacroCallKind::Attr { attr_args, invoc_attr_index, is_derive, .. } => { + // FIXME: handle `cfg_attr` let attr = item .doc_comments_and_attrs() - .nth(*invoc_attr_index as usize) + .nth(invoc_attr_index.ast_index()) .and_then(Either::left)?; match attr.token_tree() { Some(token_tree) @@ -1031,3 +1051,5 @@ impl ExpandTo { pub struct UnresolvedMacro { pub path: ModPath, } + +intern::impl_internable!(ModPath, attrs::AttrInput); diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/name.rs b/src/tools/rust-analyzer/crates/hir-expand/src/name.rs index e8b3e312a..c3462beac 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/name.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/name.rs @@ -2,7 +2,7 @@ use std::fmt; -use syntax::{ast, SmolStr, SyntaxKind}; +use syntax::{ast, utils::is_raw_identifier, SmolStr}; /// `Name` is a wrapper around string, which is used in hir for both references /// and declarations. In theory, names should also carry hygiene info, but we are @@ -33,11 +33,6 @@ impl fmt::Display for Name { } } -fn is_raw_identifier(name: &str) -> bool { - let is_keyword = SyntaxKind::from_keyword(name).is_some(); - is_keyword && !matches!(name, "self" | "crate" | "super" | "Self") -} - impl<'a> fmt::Display for UnescapedName<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.0 .0 { @@ -133,6 +128,14 @@ impl Name { } } + /// Returns the text this name represents if it isn't a tuple field. + pub fn as_str(&self) -> Option<&str> { + match &self.0 { + Repr::Text(it) => Some(it), + _ => None, + } + } + /// Returns the textual representation of this name as a [`SmolStr`]. /// Prefer using this over [`ToString::to_string`] if possible as this conversion is cheaper in /// the general case. @@ -183,7 +186,7 @@ impl AsName for ast::NameOrNameRef { } } -impl AsName for tt::Ident { +impl<Span> AsName for tt::Ident<Span> { fn as_name(&self) -> Name { Name::resolve(&self.text) } @@ -339,6 +342,7 @@ pub mod known { recursion_limit, feature, // known methods of lang items + call_once, eq, ne, ge, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs index 5afdcc0e6..3f4d2540c 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs @@ -3,7 +3,7 @@ use base_db::{CrateId, ProcMacroExpansionError, ProcMacroId, ProcMacroKind}; use stdx::never; -use crate::{db::AstDatabase, ExpandError, ExpandResult}; +use crate::{db::AstDatabase, tt, ExpandError, ExpandResult}; #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub struct ProcMacroExpander { @@ -39,7 +39,10 @@ impl ProcMacroExpander { Ok(proc_macros) => proc_macros, Err(_) => { never!("Non-dummy expander even though there are no proc macros"); - return ExpandResult::only_err(ExpandError::Other("Internal error".into())); + return ExpandResult::with_err( + tt::Subtree::empty(), + ExpandError::Other("Internal error".into()), + ); } }; let proc_macro = match proc_macros.get(id.0 as usize) { @@ -50,7 +53,10 @@ impl ProcMacroExpander { proc_macros.len(), id.0 ); - return ExpandResult::only_err(ExpandError::Other("Internal error".into())); + return ExpandResult::with_err( + tt::Subtree::empty(), + ExpandError::Other("Internal error".into()), + ); } }; @@ -69,13 +75,17 @@ impl ProcMacroExpander { } } ProcMacroExpansionError::System(text) - | ProcMacroExpansionError::Panic(text) => { - ExpandResult::only_err(ExpandError::Other(text.into())) - } + | ProcMacroExpansionError::Panic(text) => ExpandResult::with_err( + tt::Subtree::empty(), + ExpandError::Other(text.into()), + ), }, } } - None => ExpandResult::only_err(ExpandError::UnresolvedProcMacro(self.krate)), + None => ExpandResult::with_err( + tt::Subtree::empty(), + ExpandError::UnresolvedProcMacro(self.krate), + ), } } } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/quote.rs b/src/tools/rust-analyzer/crates/hir-expand/src/quote.rs index c0a7bc7ca..63586f9da 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/quote.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/quote.rs @@ -9,17 +9,18 @@ #[macro_export] macro_rules! __quote { () => { - Vec::<tt::TokenTree>::new() + Vec::<crate::tt::TokenTree>::new() }; ( @SUBTREE $delim:ident $($tt:tt)* ) => { { let children = $crate::__quote!($($tt)*); - tt::Subtree { - delimiter: Some(tt::Delimiter { - kind: tt::DelimiterKind::$delim, - id: tt::TokenId::unspecified(), - }), + crate::tt::Subtree { + delimiter: crate::tt::Delimiter { + kind: crate::tt::DelimiterKind::$delim, + open: crate::tt::TokenId::unspecified(), + close: crate::tt::TokenId::unspecified(), + }, token_trees: $crate::quote::IntoTt::to_tokens(children), } } @@ -28,10 +29,10 @@ macro_rules! __quote { ( @PUNCT $first:literal ) => { { vec![ - tt::Leaf::Punct(tt::Punct { + crate::tt::Leaf::Punct(crate::tt::Punct { char: $first, - spacing: tt::Spacing::Alone, - id: tt::TokenId::unspecified(), + spacing: crate::tt::Spacing::Alone, + span: crate::tt::TokenId::unspecified(), }).into() ] } @@ -40,15 +41,15 @@ macro_rules! __quote { ( @PUNCT $first:literal, $sec:literal ) => { { vec![ - tt::Leaf::Punct(tt::Punct { + crate::tt::Leaf::Punct(crate::tt::Punct { char: $first, - spacing: tt::Spacing::Joint, - id: tt::TokenId::unspecified(), + spacing: crate::tt::Spacing::Joint, + span: crate::tt::TokenId::unspecified(), }).into(), - tt::Leaf::Punct(tt::Punct { + crate::tt::Leaf::Punct(crate::tt::Punct { char: $sec, - spacing: tt::Spacing::Alone, - id: tt::TokenId::unspecified(), + spacing: crate::tt::Spacing::Alone, + span: crate::tt::TokenId::unspecified(), }).into() ] } @@ -67,7 +68,7 @@ macro_rules! __quote { ( ## $first:ident $($tail:tt)* ) => { { - let mut tokens = $first.into_iter().map($crate::quote::ToTokenTree::to_token).collect::<Vec<tt::TokenTree>>(); + let mut tokens = $first.into_iter().map($crate::quote::ToTokenTree::to_token).collect::<Vec<crate::tt::TokenTree>>(); let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($($tail)*)); tokens.append(&mut tail_tokens); tokens @@ -86,9 +87,9 @@ macro_rules! __quote { // Ident ( $tt:ident ) => { vec![ { - tt::Leaf::Ident(tt::Ident { + crate::tt::Leaf::Ident(crate::tt::Ident { text: stringify!($tt).into(), - id: tt::TokenId::unspecified(), + span: crate::tt::TokenId::unspecified(), }).into() }] }; @@ -127,42 +128,42 @@ macro_rules! quote { } pub(crate) trait IntoTt { - fn to_subtree(self) -> tt::Subtree; - fn to_tokens(self) -> Vec<tt::TokenTree>; + fn to_subtree(self) -> crate::tt::Subtree; + fn to_tokens(self) -> Vec<crate::tt::TokenTree>; } -impl IntoTt for Vec<tt::TokenTree> { - fn to_subtree(self) -> tt::Subtree { - tt::Subtree { delimiter: None, token_trees: self } +impl IntoTt for Vec<crate::tt::TokenTree> { + fn to_subtree(self) -> crate::tt::Subtree { + crate::tt::Subtree { delimiter: crate::tt::Delimiter::unspecified(), token_trees: self } } - fn to_tokens(self) -> Vec<tt::TokenTree> { + fn to_tokens(self) -> Vec<crate::tt::TokenTree> { self } } -impl IntoTt for tt::Subtree { - fn to_subtree(self) -> tt::Subtree { +impl IntoTt for crate::tt::Subtree { + fn to_subtree(self) -> crate::tt::Subtree { self } - fn to_tokens(self) -> Vec<tt::TokenTree> { - vec![tt::TokenTree::Subtree(self)] + fn to_tokens(self) -> Vec<crate::tt::TokenTree> { + vec![crate::tt::TokenTree::Subtree(self)] } } pub(crate) trait ToTokenTree { - fn to_token(self) -> tt::TokenTree; + fn to_token(self) -> crate::tt::TokenTree; } -impl ToTokenTree for tt::TokenTree { - fn to_token(self) -> tt::TokenTree { +impl ToTokenTree for crate::tt::TokenTree { + fn to_token(self) -> crate::tt::TokenTree { self } } -impl ToTokenTree for tt::Subtree { - fn to_token(self) -> tt::TokenTree { +impl ToTokenTree for crate::tt::Subtree { + fn to_token(self) -> crate::tt::TokenTree { self.into() } } @@ -171,15 +172,15 @@ macro_rules! impl_to_to_tokentrees { ($($ty:ty => $this:ident $im:block);*) => { $( impl ToTokenTree for $ty { - fn to_token($this) -> tt::TokenTree { - let leaf: tt::Leaf = $im.into(); + fn to_token($this) -> crate::tt::TokenTree { + let leaf: crate::tt::Leaf = $im.into(); leaf.into() } } impl ToTokenTree for &$ty { - fn to_token($this) -> tt::TokenTree { - let leaf: tt::Leaf = $im.clone().into(); + fn to_token($this) -> crate::tt::TokenTree { + let leaf: crate::tt::Leaf = $im.clone().into(); leaf.into() } } @@ -188,16 +189,16 @@ macro_rules! impl_to_to_tokentrees { } impl_to_to_tokentrees! { - u32 => self { tt::Literal{text: self.to_string().into(), id: tt::TokenId::unspecified()} }; - usize => self { tt::Literal{text: self.to_string().into(), id: tt::TokenId::unspecified()} }; - i32 => self { tt::Literal{text: self.to_string().into(), id: tt::TokenId::unspecified()} }; - bool => self { tt::Ident{text: self.to_string().into(), id: tt::TokenId::unspecified()} }; - tt::Leaf => self { self }; - tt::Literal => self { self }; - tt::Ident => self { self }; - tt::Punct => self { self }; - &str => self { tt::Literal{text: format!("\"{}\"", self.escape_default()).into(), id: tt::TokenId::unspecified()}}; - String => self { tt::Literal{text: format!("\"{}\"", self.escape_default()).into(), id: tt::TokenId::unspecified()}} + u32 => self { crate::tt::Literal{text: self.to_string().into(), span: crate::tt::TokenId::unspecified()} }; + usize => self { crate::tt::Literal{text: self.to_string().into(), span: crate::tt::TokenId::unspecified()} }; + i32 => self { crate::tt::Literal{text: self.to_string().into(), span: crate::tt::TokenId::unspecified()} }; + bool => self { crate::tt::Ident{text: self.to_string().into(), span: crate::tt::TokenId::unspecified()} }; + crate::tt::Leaf => self { self }; + crate::tt::Literal => self { self }; + crate::tt::Ident => self { self }; + crate::tt::Punct => self { self }; + &str => self { crate::tt::Literal{text: format!("\"{}\"", self.escape_default()).into(), span: crate::tt::TokenId::unspecified()}}; + String => self { crate::tt::Literal{text: format!("\"{}\"", self.escape_default()).into(), span: crate::tt::TokenId::unspecified()}} } #[cfg(test)] @@ -223,8 +224,8 @@ mod tests { assert_eq!(quote!(#s).to_string(), "\"hello\""); } - fn mk_ident(name: &str) -> tt::Ident { - tt::Ident { text: name.into(), id: tt::TokenId::unspecified() } + fn mk_ident(name: &str) -> crate::tt::Ident { + crate::tt::Ident { text: name.into(), span: crate::tt::TokenId::unspecified() } } #[test] @@ -234,7 +235,7 @@ mod tests { let quoted = quote!(#a); assert_eq!(quoted.to_string(), "hello"); let t = format!("{quoted:?}"); - assert_eq!(t, "SUBTREE $\n IDENT hello 4294967295"); + assert_eq!(t, "SUBTREE $$ 4294967295 4294967295\n IDENT hello 4294967295"); } #[test] @@ -263,11 +264,12 @@ mod tests { let fields = [mk_ident("name"), mk_ident("id")]; let fields = fields.iter().flat_map(|it| quote!(#it: self.#it.clone(), ).token_trees); - let list = tt::Subtree { - delimiter: Some(tt::Delimiter { - kind: tt::DelimiterKind::Brace, - id: tt::TokenId::unspecified(), - }), + let list = crate::tt::Subtree { + delimiter: crate::tt::Delimiter { + kind: crate::tt::DelimiterKind::Brace, + open: crate::tt::TokenId::unspecified(), + close: crate::tt::TokenId::unspecified(), + }, token_trees: fields.collect(), }; diff --git a/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml b/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml index ae837ac6d..a8b8d5222 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml +++ b/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml @@ -2,9 +2,11 @@ name = "hir-ty" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -14,7 +16,7 @@ cov-mark = "2.0.0-pre.1" itertools = "0.10.5" arrayvec = "0.7.2" bitflags = "1.3.2" -smallvec = "1.10.0" +smallvec.workspace = true ena = "0.14.0" tracing = "0.1.35" rustc-hash = "1.1.0" @@ -24,20 +26,21 @@ chalk-ir = "0.88.0" chalk-recursive = { version = "0.88.0", default-features = false } chalk-derive = "0.88.0" la-arena = { version = "0.3.0", path = "../../lib/la-arena" } -once_cell = "1.15.0" +once_cell = "1.17.0" typed-arena = "2.0.1" rustc_index = { version = "0.0.20221221", package = "hkalbasi-rustc-ap-rustc_index", default-features = false } -stdx = { path = "../stdx", version = "0.0.0" } -hir-def = { path = "../hir-def", version = "0.0.0" } -hir-expand = { path = "../hir-expand", version = "0.0.0" } -base-db = { path = "../base-db", version = "0.0.0" } -profile = { path = "../profile", version = "0.0.0" } -syntax = { path = "../syntax", version = "0.0.0" } -limit = { path = "../limit", version = "0.0.0" } +# local deps +stdx.workspace = true +intern.workspace = true +hir-def.workspace = true +hir-expand.workspace = true +base-db.workspace = true +profile.workspace = true +syntax.workspace = true +limit.workspace = true [dev-dependencies] -test-utils = { path = "../test-utils" } expect-test = "1.4.0" tracing = "0.1.35" tracing-subscriber = { version = "0.3.16", default-features = false, features = [ @@ -45,3 +48,7 @@ tracing-subscriber = { version = "0.3.16", default-features = false, features = "registry", ] } tracing-tree = "0.2.1" +project-model = { path = "../project-model" } + +# local deps +test-utils.workspace = true diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs b/src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs index cbcf8f74c..58744dd0c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs @@ -6,9 +6,9 @@ use std::sync::Arc; use chalk_ir::cast::Cast; +use hir_def::lang_item::LangItem; use hir_expand::name::name; use limit::Limit; -use syntax::SmolStr; use crate::{ db::HirDatabase, infer::unify::InferenceTable, Canonical, Goal, Interner, ProjectionTyExt, @@ -17,11 +17,13 @@ use crate::{ static AUTODEREF_RECURSION_LIMIT: Limit = Limit::new(10); +#[derive(Debug)] pub(crate) enum AutoderefKind { Builtin, Overloaded, } +#[derive(Debug)] pub(crate) struct Autoderef<'a, 'db> { pub(crate) table: &'a mut InferenceTable<'db>, ty: Ty, @@ -117,9 +119,8 @@ fn deref_by_trait(table: &mut InferenceTable<'_>, ty: Ty) -> Option<Ty> { } let db = table.db; - let deref_trait = db - .lang_item(table.trait_env.krate, SmolStr::new_inline("deref")) - .and_then(|l| l.as_trait())?; + let deref_trait = + db.lang_item(table.trait_env.krate, LangItem::Deref).and_then(|l| l.as_trait())?; let target = db.trait_data(deref_trait).associated_type_by_name(&name![Target])?; let projection = { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/builder.rs b/src/tools/rust-analyzer/crates/hir-ty/src/builder.rs index d5ef0c22d..8faef7bf7 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/builder.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/builder.rs @@ -63,7 +63,7 @@ impl<D> TyBuilder<D> { } fn build_internal(self) -> (D, Substitution) { - assert_eq!(self.vec.len(), self.param_kinds.len()); + assert_eq!(self.vec.len(), self.param_kinds.len(), "{:?}", &self.param_kinds); for (a, e) in self.vec.iter().zip(self.param_kinds.iter()) { self.assert_match_kind(a, e); } @@ -282,6 +282,21 @@ impl TyBuilder<Tuple> { let (Tuple(size), subst) = self.build_internal(); TyKind::Tuple(size, subst).intern(Interner) } + + pub fn tuple_with<I>(elements: I) -> Ty + where + I: IntoIterator<Item = Ty>, + <I as IntoIterator>::IntoIter: ExactSizeIterator, + { + let elements = elements.into_iter(); + let len = elements.len(); + let mut b = + TyBuilder::new(Tuple(len), iter::repeat(ParamKind::Type).take(len).collect(), None); + for e in elements { + b = b.push(e); + } + b.build() + } } impl TyBuilder<TraitId> { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs index 1c2b8de7f..6989e9fb9 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs @@ -3,7 +3,6 @@ use std::sync::Arc; use cov_mark::hit; -use syntax::SmolStr; use tracing::debug; use chalk_ir::{cast::Cast, fold::shift::Shift, CanonicalVarKinds}; @@ -12,7 +11,7 @@ use chalk_solve::rust_ir::{self, OpaqueTyDatumBound, WellKnownTrait}; use base_db::CrateId; use hir_def::{ expr::Movability, - lang_item::{lang_attr, LangItemTarget}, + lang_item::{lang_attr, LangItem, LangItemTarget}, AssocItemId, GenericDefId, HasModule, ItemContainerId, Lookup, ModuleId, TypeAliasId, }; use hir_expand::name::name; @@ -182,9 +181,9 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> { &self, well_known_trait: rust_ir::WellKnownTrait, ) -> Option<chalk_ir::TraitId<Interner>> { - let lang_attr = lang_attr_from_well_known_trait(well_known_trait); + let lang_attr = lang_item_from_well_known_trait(well_known_trait); let trait_ = match self.db.lang_item(self.krate, lang_attr.into()) { - Some(LangItemTarget::TraitId(trait_)) => trait_, + Some(LangItemTarget::Trait(trait_)) => trait_, _ => return None, }; Some(to_chalk_trait_id(trait_)) @@ -206,7 +205,7 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> { .return_type_impl_traits(func) .expect("impl trait id without impl traits"); let (datas, binders) = (*datas).as_ref().into_value_and_skipped_binders(); - let data = &datas.impl_traits[idx as usize]; + let data = &datas.impl_traits[idx]; let bound = OpaqueTyDatumBound { bounds: make_single_type_binders(data.bounds.skip_binders().to_vec()), where_clauses: chalk_ir::Binders::empty(Interner, vec![]), @@ -216,7 +215,7 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> { crate::ImplTraitId::AsyncBlockTypeImplTrait(..) => { if let Some((future_trait, future_output)) = self .db - .lang_item(self.krate, SmolStr::new_inline("future_trait")) + .lang_item(self.krate, LangItem::Future) .and_then(|item| item.as_trait()) .and_then(|trait_| { let alias = @@ -246,7 +245,7 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> { binder.push(crate::wrap_empty_binders(impl_bound)); let sized_trait = self .db - .lang_item(self.krate, SmolStr::new_inline("sized")) + .lang_item(self.krate, LangItem::Sized) .and_then(|item| item.as_trait()); if let Some(sized_trait_) = sized_trait { let sized_bound = WhereClause::Implemented(TraitRef { @@ -493,7 +492,7 @@ pub(crate) fn associated_ty_data_query( if !ctx.unsized_types.borrow().contains(&self_ty) { let sized_trait = db - .lang_item(resolver.krate(), SmolStr::new_inline("sized")) + .lang_item(resolver.krate(), LangItem::Sized) .and_then(|lang_item| lang_item.as_trait().map(to_chalk_trait_id)); let sized_bound = sized_trait.into_iter().map(|sized_trait| { let trait_bound = @@ -541,8 +540,8 @@ pub(crate) fn trait_datum_query( let where_clauses = convert_where_clauses(db, trait_.into(), &bound_vars); let associated_ty_ids = trait_data.associated_types().map(to_assoc_type_id).collect(); let trait_datum_bound = rust_ir::TraitDatumBound { where_clauses }; - let well_known = - lang_attr(db.upcast(), trait_).and_then(|name| well_known_trait_from_lang_attr(&name)); + let well_known = lang_attr(db.upcast(), trait_) + .and_then(|name| well_known_trait_from_lang_item(LangItem::from_str(&name)?)); let trait_datum = TraitDatum { id: trait_id, binders: make_binders(db, &generic_params, trait_datum_bound), @@ -553,42 +552,42 @@ pub(crate) fn trait_datum_query( Arc::new(trait_datum) } -fn well_known_trait_from_lang_attr(name: &str) -> Option<WellKnownTrait> { - Some(match name { - "clone" => WellKnownTrait::Clone, - "coerce_unsized" => WellKnownTrait::CoerceUnsized, - "copy" => WellKnownTrait::Copy, - "discriminant_kind" => WellKnownTrait::DiscriminantKind, - "dispatch_from_dyn" => WellKnownTrait::DispatchFromDyn, - "drop" => WellKnownTrait::Drop, - "fn" => WellKnownTrait::Fn, - "fn_mut" => WellKnownTrait::FnMut, - "fn_once" => WellKnownTrait::FnOnce, - "generator" => WellKnownTrait::Generator, - "sized" => WellKnownTrait::Sized, - "unpin" => WellKnownTrait::Unpin, - "unsize" => WellKnownTrait::Unsize, - "tuple_trait" => WellKnownTrait::Tuple, +fn well_known_trait_from_lang_item(item: LangItem) -> Option<WellKnownTrait> { + Some(match item { + LangItem::Clone => WellKnownTrait::Clone, + LangItem::CoerceUnsized => WellKnownTrait::CoerceUnsized, + LangItem::Copy => WellKnownTrait::Copy, + LangItem::DiscriminantKind => WellKnownTrait::DiscriminantKind, + LangItem::DispatchFromDyn => WellKnownTrait::DispatchFromDyn, + LangItem::Drop => WellKnownTrait::Drop, + LangItem::Fn => WellKnownTrait::Fn, + LangItem::FnMut => WellKnownTrait::FnMut, + LangItem::FnOnce => WellKnownTrait::FnOnce, + LangItem::Generator => WellKnownTrait::Generator, + LangItem::Sized => WellKnownTrait::Sized, + LangItem::Unpin => WellKnownTrait::Unpin, + LangItem::Unsize => WellKnownTrait::Unsize, + LangItem::Tuple => WellKnownTrait::Tuple, _ => return None, }) } -fn lang_attr_from_well_known_trait(attr: WellKnownTrait) -> &'static str { - match attr { - WellKnownTrait::Clone => "clone", - WellKnownTrait::CoerceUnsized => "coerce_unsized", - WellKnownTrait::Copy => "copy", - WellKnownTrait::DiscriminantKind => "discriminant_kind", - WellKnownTrait::DispatchFromDyn => "dispatch_from_dyn", - WellKnownTrait::Drop => "drop", - WellKnownTrait::Fn => "fn", - WellKnownTrait::FnMut => "fn_mut", - WellKnownTrait::FnOnce => "fn_once", - WellKnownTrait::Generator => "generator", - WellKnownTrait::Sized => "sized", - WellKnownTrait::Tuple => "tuple_trait", - WellKnownTrait::Unpin => "unpin", - WellKnownTrait::Unsize => "unsize", +fn lang_item_from_well_known_trait(trait_: WellKnownTrait) -> LangItem { + match trait_ { + WellKnownTrait::Clone => LangItem::Clone, + WellKnownTrait::CoerceUnsized => LangItem::CoerceUnsized, + WellKnownTrait::Copy => LangItem::Copy, + WellKnownTrait::DiscriminantKind => LangItem::DiscriminantKind, + WellKnownTrait::DispatchFromDyn => LangItem::DispatchFromDyn, + WellKnownTrait::Drop => LangItem::Drop, + WellKnownTrait::Fn => LangItem::Fn, + WellKnownTrait::FnMut => LangItem::FnMut, + WellKnownTrait::FnOnce => LangItem::FnOnce, + WellKnownTrait::Generator => LangItem::Generator, + WellKnownTrait::Sized => LangItem::Sized, + WellKnownTrait::Tuple => LangItem::Tuple, + WellKnownTrait::Unpin => LangItem::Unpin, + WellKnownTrait::Unsize => LangItem::Unsize, } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs index 996b42f5b..45c975dfc 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs @@ -1,13 +1,13 @@ //! Various extensions traits for Chalk types. -use chalk_ir::{FloatTy, IntTy, Mutability, Scalar, UintTy}; +use chalk_ir::{FloatTy, IntTy, Mutability, Scalar, TyVariableKind, UintTy}; use hir_def::{ builtin_type::{BuiltinFloat, BuiltinInt, BuiltinType, BuiltinUint}, generics::TypeOrConstParamData, + lang_item::LangItem, type_ref::Rawness, FunctionId, GenericDefId, HasModule, ItemContainerId, Lookup, TraitId, }; -use syntax::SmolStr; use crate::{ db::HirDatabase, from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, @@ -18,6 +18,8 @@ use crate::{ pub trait TyExt { fn is_unit(&self) -> bool; + fn is_integral(&self) -> bool; + fn is_floating_point(&self) -> bool; fn is_never(&self) -> bool; fn is_unknown(&self) -> bool; fn is_ty_var(&self) -> bool; @@ -51,6 +53,21 @@ impl TyExt for Ty { matches!(self.kind(Interner), TyKind::Tuple(0, _)) } + fn is_integral(&self) -> bool { + matches!( + self.kind(Interner), + TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_)) + | TyKind::InferenceVar(_, TyVariableKind::Integer) + ) + } + + fn is_floating_point(&self) -> bool { + matches!( + self.kind(Interner), + TyKind::Scalar(Scalar::Float(_)) | TyKind::InferenceVar(_, TyVariableKind::Float) + ) + } + fn is_never(&self) -> bool { matches!(self.kind(Interner), TyKind::Never) } @@ -197,9 +214,8 @@ impl TyExt for Ty { match db.lookup_intern_impl_trait_id((*opaque_ty_id).into()) { ImplTraitId::AsyncBlockTypeImplTrait(def, _expr) => { let krate = def.module(db.upcast()).krate(); - if let Some(future_trait) = db - .lang_item(krate, SmolStr::new_inline("future_trait")) - .and_then(|item| item.as_trait()) + if let Some(future_trait) = + db.lang_item(krate, LangItem::Future).and_then(|item| item.as_trait()) { // This is only used by type walking. // Parameters will be walked outside, and projection predicate is not used. @@ -218,9 +234,8 @@ impl TyExt for Ty { } ImplTraitId::ReturnTypeImplTrait(func, idx) => { db.return_type_impl_traits(func).map(|it| { - let data = (*it) - .as_ref() - .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone()); + let data = + (*it).as_ref().map(|rpit| rpit.impl_traits[idx].bounds.clone()); data.substitute(Interner, &subst).into_value_and_skipped_binders().0 }) } @@ -231,9 +246,8 @@ impl TyExt for Ty { { ImplTraitId::ReturnTypeImplTrait(func, idx) => { db.return_type_impl_traits(func).map(|it| { - let data = (*it) - .as_ref() - .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone()); + let data = + (*it).as_ref().map(|rpit| rpit.impl_traits[idx].bounds.clone()); data.substitute(Interner, &opaque_ty.substitution) }) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs index 54b244620..d45e2a943 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs @@ -65,7 +65,7 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> { fn layout_of_adt(&self, def: AdtId, subst: Substitution) -> Result<Layout, LayoutError>; #[salsa::invoke(crate::layout::target_data_layout_query)] - fn target_data_layout(&self, krate: CrateId) -> Arc<TargetDataLayout>; + fn target_data_layout(&self, krate: CrateId) -> Option<Arc<TargetDataLayout>>; #[salsa::invoke(crate::lower::callable_item_sig)] fn callable_item_signature(&self, def: CallableDefId) -> PolyFnSig; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check/case_conv.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check/case_conv.rs index 88d607194..2c1368962 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check/case_conv.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check/case_conv.rs @@ -162,6 +162,7 @@ mod tests { check(to_lower_snake_case, "a", expect![[""]]); check(to_lower_snake_case, "abc", expect![[""]]); check(to_lower_snake_case, "foo__bar", expect![["foo_bar"]]); + check(to_lower_snake_case, "Δ", expect!["δ"]); } #[test] @@ -195,5 +196,6 @@ mod tests { check(to_upper_snake_case, "X86_64", expect![[""]]); check(to_upper_snake_case, "FOO_BAr", expect![["FOO_BAR"]]); check(to_upper_snake_case, "FOO__BAR", expect![["FOO_BAR"]]); + check(to_upper_snake_case, "ß", expect!["SS"]); } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs index c8df4c796..3286dcb5a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs @@ -5,7 +5,9 @@ use std::fmt; use std::sync::Arc; -use hir_def::{path::path, resolver::HasResolver, AdtId, AssocItemId, DefWithBodyId, HasModule}; +use hir_def::lang_item::LangItem; +use hir_def::{resolver::HasResolver, AdtId, AssocItemId, DefWithBodyId, HasModule}; +use hir_def::{ItemContainerId, Lookup}; use hir_expand::name; use itertools::Either; use itertools::Itertools; @@ -245,26 +247,25 @@ struct FilterMapNextChecker { impl FilterMapNextChecker { fn new(resolver: &hir_def::resolver::Resolver, db: &dyn HirDatabase) -> Self { // Find and store the FunctionIds for Iterator::filter_map and Iterator::next - let iterator_path = path![core::iter::Iterator]; - let mut filter_map_function_id = None; - let mut next_function_id = None; - - if let Some(iterator_trait_id) = resolver.resolve_known_trait(db.upcast(), &iterator_path) { - let iterator_trait_items = &db.trait_data(iterator_trait_id).items; - for item in iterator_trait_items.iter() { - if let (name, AssocItemId::FunctionId(id)) = item { - if *name == name![filter_map] { - filter_map_function_id = Some(*id); + let (next_function_id, filter_map_function_id) = match db + .lang_item(resolver.krate(), LangItem::IteratorNext) + .and_then(|it| it.as_function()) + { + Some(next_function_id) => ( + Some(next_function_id), + match next_function_id.lookup(db.upcast()).container { + ItemContainerId::TraitId(iterator_trait_id) => { + let iterator_trait_items = &db.trait_data(iterator_trait_id).items; + iterator_trait_items.iter().find_map(|(name, it)| match it { + &AssocItemId::FunctionId(id) if *name == name![filter_map] => Some(id), + _ => None, + }) } - if *name == name![next] { - next_function_id = Some(*id); - } - } - if filter_map_function_id.is_some() && next_function_id.is_some() { - break; - } - } - } + _ => None, + }, + ), + None => (None, None), + }; Self { filter_map_function_id, next_function_id, prev_filter_map_expr_id: None } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs index 66e813eed..b22064d8c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs @@ -11,17 +11,17 @@ use hir_def::{ db::DefDatabase, find_path, generics::{TypeOrConstParamData, TypeParamProvenance}, - intern::{Internable, Interned}, item_scope::ItemInNs, + lang_item::{LangItem, LangItemTarget}, path::{Path, PathKind}, type_ref::{ConstScalar, TraitBoundModifier, TypeBound, TypeRef}, visibility::Visibility, HasModule, ItemContainerId, Lookup, ModuleDefId, ModuleId, TraitId, }; use hir_expand::{hygiene::Hygiene, name::Name}; +use intern::{Internable, Interned}; use itertools::Itertools; use smallvec::SmallVec; -use syntax::SmolStr; use crate::{ db::HirDatabase, @@ -325,7 +325,7 @@ impl HirDisplay for ProjectionTy { let trait_ref = self.trait_ref(f.db); write!(f, "<")?; - fmt_trait_ref(&trait_ref, f, true)?; + fmt_trait_ref(f, &trait_ref, true)?; write!(f, ">::{}", f.db.type_alias_data(from_assoc_type_id(self.associated_ty_id)).name)?; let proj_params_count = self.substitution.len(Interner) - trait_ref.substitution.len(Interner); @@ -383,7 +383,10 @@ impl HirDisplay for BoundVar { } impl HirDisplay for Ty { - fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { + fn hir_fmt( + &self, + f @ &mut HirFormatter { db, .. }: &mut HirFormatter<'_>, + ) -> Result<(), HirDisplayError> { if f.should_truncate() { return write!(f, "{TYPE_HINT_TRUNCATION}"); } @@ -434,7 +437,7 @@ impl HirDisplay for Ty { bounds.iter().any(|bound| { if let WhereClause::Implemented(trait_ref) = bound.skip_binders() { let trait_ = trait_ref.hir_trait_id(); - fn_traits(f.db.upcast(), trait_).any(|it| it == trait_) + fn_traits(db.upcast(), trait_).any(|it| it == trait_) } else { false } @@ -450,22 +453,20 @@ impl HirDisplay for Ty { substitution: parameters, })) | TyKind::OpaqueType(opaque_ty_id, parameters) => { - let impl_trait_id = - f.db.lookup_intern_impl_trait_id((*opaque_ty_id).into()); + let impl_trait_id = db.lookup_intern_impl_trait_id((*opaque_ty_id).into()); if let ImplTraitId::ReturnTypeImplTrait(func, idx) = impl_trait_id { - let datas = - f.db.return_type_impl_traits(func) - .expect("impl trait id without data"); - let data = (*datas) - .as_ref() - .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone()); + let datas = db + .return_type_impl_traits(func) + .expect("impl trait id without data"); + let data = + (*datas).as_ref().map(|rpit| rpit.impl_traits[idx].bounds.clone()); let bounds = data.substitute(Interner, parameters); let mut len = bounds.skip_binders().len(); // Don't count Sized but count when it absent // (i.e. when explicit ?Sized bound is set). let default_sized = SizedByDefault::Sized { - anchor: func.lookup(f.db.upcast()).module(f.db.upcast()).krate(), + anchor: func.lookup(db.upcast()).module(db.upcast()).krate(), }; let sized_bounds = bounds .skip_binders() @@ -476,7 +477,7 @@ impl HirDisplay for Ty { WhereClause::Implemented(trait_ref) if default_sized.is_sized_trait( trait_ref.hir_trait_id(), - f.db.upcast(), + db.upcast(), ), ) }) @@ -524,19 +525,19 @@ impl HirDisplay for Ty { sig.hir_fmt(f)?; } TyKind::FnDef(def, parameters) => { - let def = from_chalk(f.db, *def); - let sig = f.db.callable_item_signature(def).substitute(Interner, parameters); + let def = from_chalk(db, *def); + let sig = db.callable_item_signature(def).substitute(Interner, parameters); + f.start_location_link(def.into()); match def { - CallableDefId::FunctionId(ff) => { - write!(f, "fn {}", f.db.function_data(ff).name)? - } - CallableDefId::StructId(s) => write!(f, "{}", f.db.struct_data(s).name)?, + CallableDefId::FunctionId(ff) => write!(f, "fn {}", db.function_data(ff).name)?, + CallableDefId::StructId(s) => write!(f, "{}", db.struct_data(s).name)?, CallableDefId::EnumVariantId(e) => { - write!(f, "{}", f.db.enum_data(e.parent).variants[e.local_id].name)? + write!(f, "{}", db.enum_data(e.parent).variants[e.local_id].name)? } }; + f.end_location_link(); if parameters.len(Interner) > 0 { - let generics = generics(f.db.upcast(), def.into()); + let generics = generics(db.upcast(), def.into()); let (parent_params, self_param, type_params, const_params, _impl_trait_params) = generics.provenance_split(); let total_len = parent_params + self_param + type_params + const_params; @@ -568,15 +569,15 @@ impl HirDisplay for Ty { match f.display_target { DisplayTarget::Diagnostics | DisplayTarget::Test => { let name = match *def_id { - hir_def::AdtId::StructId(it) => f.db.struct_data(it).name.clone(), - hir_def::AdtId::UnionId(it) => f.db.union_data(it).name.clone(), - hir_def::AdtId::EnumId(it) => f.db.enum_data(it).name.clone(), + hir_def::AdtId::StructId(it) => db.struct_data(it).name.clone(), + hir_def::AdtId::UnionId(it) => db.union_data(it).name.clone(), + hir_def::AdtId::EnumId(it) => db.enum_data(it).name.clone(), }; write!(f, "{name}")?; } DisplayTarget::SourceCode { module_id } => { if let Some(path) = find_path::find_path( - f.db.upcast(), + db.upcast(), ItemInNs::Types((*def_id).into()), module_id, false, @@ -596,8 +597,8 @@ impl HirDisplay for Ty { || f.omit_verbose_types() { match self - .as_generic_def(f.db) - .map(|generic_def_id| f.db.generic_defaults(generic_def_id)) + .as_generic_def(db) + .map(|generic_def_id| db.generic_defaults(generic_def_id)) .filter(|defaults| !defaults.is_empty()) { None => parameters.as_slice(Interner), @@ -669,16 +670,23 @@ impl HirDisplay for Ty { } TyKind::AssociatedType(assoc_type_id, parameters) => { let type_alias = from_assoc_type_id(*assoc_type_id); - let trait_ = match type_alias.lookup(f.db.upcast()).container { + let trait_ = match type_alias.lookup(db.upcast()).container { ItemContainerId::TraitId(it) => it, _ => panic!("not an associated type"), }; - let trait_ = f.db.trait_data(trait_); - let type_alias_data = f.db.type_alias_data(type_alias); + let trait_data = db.trait_data(trait_); + let type_alias_data = db.type_alias_data(type_alias); // Use placeholder associated types when the target is test (https://rust-lang.github.io/chalk/book/clauses/type_equality.html#placeholder-associated-types) if f.display_target.is_test() { - write!(f, "{}::{}", trait_.name, type_alias_data.name)?; + f.start_location_link(trait_.into()); + write!(f, "{}", trait_data.name)?; + f.end_location_link(); + write!(f, "::")?; + + f.start_location_link(type_alias.into()); + write!(f, "{}", type_alias_data.name)?; + f.end_location_link(); // Note that the generic args for the associated type come before those for the // trait (including the self type). // FIXME: reconsider the generic args order upon formatting? @@ -697,30 +705,54 @@ impl HirDisplay for Ty { } } TyKind::Foreign(type_alias) => { - let type_alias = f.db.type_alias_data(from_foreign_def_id(*type_alias)); + let alias = from_foreign_def_id(*type_alias); + let type_alias = db.type_alias_data(alias); + f.start_location_link(alias.into()); write!(f, "{}", type_alias.name)?; + f.end_location_link(); } TyKind::OpaqueType(opaque_ty_id, parameters) => { - let impl_trait_id = f.db.lookup_intern_impl_trait_id((*opaque_ty_id).into()); + let impl_trait_id = db.lookup_intern_impl_trait_id((*opaque_ty_id).into()); match impl_trait_id { ImplTraitId::ReturnTypeImplTrait(func, idx) => { let datas = - f.db.return_type_impl_traits(func).expect("impl trait id without data"); - let data = (*datas) - .as_ref() - .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone()); + db.return_type_impl_traits(func).expect("impl trait id without data"); + let data = + (*datas).as_ref().map(|rpit| rpit.impl_traits[idx].bounds.clone()); let bounds = data.substitute(Interner, ¶meters); - let krate = func.lookup(f.db.upcast()).module(f.db.upcast()).krate(); + let krate = func.lookup(db.upcast()).module(db.upcast()).krate(); write_bounds_like_dyn_trait_with_prefix( + f, "impl", bounds.skip_binders(), SizedByDefault::Sized { anchor: krate }, - f, )?; // FIXME: it would maybe be good to distinguish this from the alias type (when debug printing), and to show the substitution } - ImplTraitId::AsyncBlockTypeImplTrait(..) => { - write!(f, "impl Future<Output = ")?; + ImplTraitId::AsyncBlockTypeImplTrait(body, ..) => { + let future_trait = db + .lang_item(body.module(db.upcast()).krate(), LangItem::Future) + .and_then(LangItemTarget::as_trait); + let output = future_trait.and_then(|t| { + db.trait_data(t).associated_type_by_name(&hir_expand::name!(Output)) + }); + write!(f, "impl ")?; + if let Some(t) = future_trait { + f.start_location_link(t.into()); + } + write!(f, "Future")?; + if let Some(_) = future_trait { + f.end_location_link(); + } + write!(f, "<")?; + if let Some(t) = output { + f.start_location_link(t.into()); + } + write!(f, "Output")?; + if let Some(_) = output { + f.end_location_link(); + } + write!(f, " = ")?; parameters.at(Interner, 0).hir_fmt(f)?; write!(f, ">")?; } @@ -732,7 +764,7 @@ impl HirDisplay for Ty { DisplaySourceCodeError::Closure, )); } - let sig = substs.at(Interner, 0).assert_ty_ref(Interner).callable_sig(f.db); + let sig = substs.at(Interner, 0).assert_ty_ref(Interner).callable_sig(db); if let Some(sig) = sig { if sig.params().is_empty() { write!(f, "||")?; @@ -751,8 +783,8 @@ impl HirDisplay for Ty { } } TyKind::Placeholder(idx) => { - let id = from_placeholder_idx(f.db, *idx); - let generics = generics(f.db.upcast(), id.parent); + let id = from_placeholder_idx(db, *idx); + let generics = generics(db.upcast(), id.parent); let param_data = &generics.params.type_or_consts[id.local_id]; match param_data { TypeOrConstParamData::TypeParamData(p) => match p.provenance { @@ -760,28 +792,28 @@ impl HirDisplay for Ty { write!(f, "{}", p.name.clone().unwrap_or_else(Name::missing))? } TypeParamProvenance::ArgumentImplTrait => { - let substs = generics.placeholder_subst(f.db); - let bounds = - f.db.generic_predicates(id.parent) - .iter() - .map(|pred| pred.clone().substitute(Interner, &substs)) - .filter(|wc| match &wc.skip_binders() { - WhereClause::Implemented(tr) => { - &tr.self_type_parameter(Interner) == self - } - WhereClause::AliasEq(AliasEq { - alias: AliasTy::Projection(proj), - ty: _, - }) => &proj.self_type_parameter(f.db) == self, - _ => false, - }) - .collect::<Vec<_>>(); - let krate = id.parent.module(f.db.upcast()).krate(); + let substs = generics.placeholder_subst(db); + let bounds = db + .generic_predicates(id.parent) + .iter() + .map(|pred| pred.clone().substitute(Interner, &substs)) + .filter(|wc| match &wc.skip_binders() { + WhereClause::Implemented(tr) => { + &tr.self_type_parameter(Interner) == self + } + WhereClause::AliasEq(AliasEq { + alias: AliasTy::Projection(proj), + ty: _, + }) => &proj.self_type_parameter(db) == self, + _ => false, + }) + .collect::<Vec<_>>(); + let krate = id.parent.module(db.upcast()).krate(); write_bounds_like_dyn_trait_with_prefix( + f, "impl", &bounds, SizedByDefault::Sized { anchor: krate }, - f, )?; } }, @@ -803,29 +835,28 @@ impl HirDisplay for Ty { bounds.extend(auto_traits); write_bounds_like_dyn_trait_with_prefix( + f, "dyn", &bounds, SizedByDefault::NotSized, - f, )?; } TyKind::Alias(AliasTy::Projection(p_ty)) => p_ty.hir_fmt(f)?, TyKind::Alias(AliasTy::Opaque(opaque_ty)) => { - let impl_trait_id = f.db.lookup_intern_impl_trait_id(opaque_ty.opaque_ty_id.into()); + let impl_trait_id = db.lookup_intern_impl_trait_id(opaque_ty.opaque_ty_id.into()); match impl_trait_id { ImplTraitId::ReturnTypeImplTrait(func, idx) => { let datas = - f.db.return_type_impl_traits(func).expect("impl trait id without data"); - let data = (*datas) - .as_ref() - .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone()); + db.return_type_impl_traits(func).expect("impl trait id without data"); + let data = + (*datas).as_ref().map(|rpit| rpit.impl_traits[idx].bounds.clone()); let bounds = data.substitute(Interner, &opaque_ty.substitution); - let krate = func.lookup(f.db.upcast()).module(f.db.upcast()).krate(); + let krate = func.lookup(db.upcast()).module(db.upcast()).krate(); write_bounds_like_dyn_trait_with_prefix( + f, "impl", bounds.skip_binders(), SizedByDefault::Sized { anchor: krate }, - f, )?; } ImplTraitId::AsyncBlockTypeImplTrait(..) => { @@ -848,7 +879,6 @@ impl HirDisplay for Ty { DisplaySourceCodeError::Generator, )); } - let subst = subst.as_slice(Interner); let a: Option<SmallVec<[&Ty; 3]>> = subst .get(subst.len() - 3..) @@ -897,7 +927,7 @@ impl HirDisplay for CallableSig { } } -fn fn_traits(db: &dyn DefDatabase, trait_: TraitId) -> impl Iterator<Item = TraitId> { +fn fn_traits(db: &dyn DefDatabase, trait_: TraitId) -> impl Iterator<Item = TraitId> + '_ { let krate = trait_.lookup(db).container.krate(); utils::fn_traits(db, krate) } @@ -914,7 +944,7 @@ impl SizedByDefault { Self::NotSized => false, Self::Sized { anchor } => { let sized_trait = db - .lang_item(anchor, SmolStr::new_inline("sized")) + .lang_item(anchor, LangItem::Sized) .and_then(|lang_item| lang_item.as_trait()); Some(trait_) == sized_trait } @@ -923,26 +953,26 @@ impl SizedByDefault { } pub fn write_bounds_like_dyn_trait_with_prefix( + f: &mut HirFormatter<'_>, prefix: &str, predicates: &[QuantifiedWhereClause], default_sized: SizedByDefault, - f: &mut HirFormatter<'_>, ) -> Result<(), HirDisplayError> { write!(f, "{prefix}")?; if !predicates.is_empty() || predicates.is_empty() && matches!(default_sized, SizedByDefault::Sized { .. }) { write!(f, " ")?; - write_bounds_like_dyn_trait(predicates, default_sized, f) + write_bounds_like_dyn_trait(f, predicates, default_sized) } else { Ok(()) } } fn write_bounds_like_dyn_trait( + f: &mut HirFormatter<'_>, predicates: &[QuantifiedWhereClause], default_sized: SizedByDefault, - f: &mut HirFormatter<'_>, ) -> Result<(), HirDisplayError> { // Note: This code is written to produce nice results (i.e. // corresponding to surface Rust) for types that can occur in @@ -978,7 +1008,9 @@ fn write_bounds_like_dyn_trait( // We assume that the self type is ^0.0 (i.e. the // existential) here, which is the only thing that's // possible in actual Rust, and hence don't print it + f.start_location_link(trait_.into()); write!(f, "{}", f.db.trait_data(trait_).name)?; + f.end_location_link(); if let [_, params @ ..] = &*trait_ref.substitution.as_slice(Interner) { if is_fn_trait { if let Some(args) = @@ -1015,7 +1047,9 @@ fn write_bounds_like_dyn_trait( if let AliasTy::Projection(proj) = alias { let assoc_ty_id = from_assoc_type_id(proj.associated_ty_id); let type_alias = f.db.type_alias_data(assoc_ty_id); + f.start_location_link(assoc_ty_id.into()); write!(f, "{}", type_alias.name)?; + f.end_location_link(); let proj_arg_count = generics(f.db.upcast(), assoc_ty_id.into()).len_self(); if proj_arg_count > 0 { @@ -1040,19 +1074,33 @@ fn write_bounds_like_dyn_trait( if angle_open { write!(f, ">")?; } - if matches!(default_sized, SizedByDefault::Sized { .. }) { + if let SizedByDefault::Sized { anchor } = default_sized { + let sized_trait = + f.db.lang_item(anchor, LangItem::Sized).and_then(|lang_item| lang_item.as_trait()); if !is_sized { - write!(f, "{}?Sized", if first { "" } else { " + " })?; + if !first { + write!(f, " + ")?; + } + if let Some(sized_trait) = sized_trait { + f.start_location_link(sized_trait.into()); + } + write!(f, "?Sized")?; } else if first { + if let Some(sized_trait) = sized_trait { + f.start_location_link(sized_trait.into()); + } write!(f, "Sized")?; } + if let Some(_) = sized_trait { + f.end_location_link(); + } } Ok(()) } fn fmt_trait_ref( - tr: &TraitRef, f: &mut HirFormatter<'_>, + tr: &TraitRef, use_as: bool, ) -> Result<(), HirDisplayError> { if f.should_truncate() { @@ -1065,7 +1113,10 @@ fn fmt_trait_ref( } else { write!(f, ": ")?; } - write!(f, "{}", f.db.trait_data(tr.hir_trait_id()).name)?; + let trait_ = tr.hir_trait_id(); + f.start_location_link(trait_.into()); + write!(f, "{}", f.db.trait_data(trait_).name)?; + f.end_location_link(); if tr.substitution.len(Interner) > 1 { write!(f, "<")?; f.write_joined(&tr.substitution.as_slice(Interner)[1..], ", ")?; @@ -1076,7 +1127,7 @@ fn fmt_trait_ref( impl HirDisplay for TraitRef { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { - fmt_trait_ref(self, f, false) + fmt_trait_ref(f, self, false) } } @@ -1090,12 +1141,13 @@ impl HirDisplay for WhereClause { WhereClause::Implemented(trait_ref) => trait_ref.hir_fmt(f)?, WhereClause::AliasEq(AliasEq { alias: AliasTy::Projection(projection_ty), ty }) => { write!(f, "<")?; - fmt_trait_ref(&projection_ty.trait_ref(f.db), f, true)?; - write!( - f, - ">::{} = ", - f.db.type_alias_data(from_assoc_type_id(projection_ty.associated_ty_id)).name, - )?; + fmt_trait_ref(f, &projection_ty.trait_ref(f.db), true)?; + write!(f, ">::",)?; + let type_alias = from_assoc_type_id(projection_ty.associated_ty_id); + f.start_location_link(type_alias.into()); + write!(f, "{}", f.db.type_alias_data(type_alias).name,)?; + f.end_location_link(); + write!(f, " = ")?; ty.hir_fmt(f)?; } WhereClause::AliasEq(_) => write!(f, "{{error}}")?, @@ -1367,7 +1419,7 @@ impl HirDisplay for Path { write!(f, "<")?; let mut first = true; - for arg in &generic_args.args { + for arg in generic_args.args.iter() { if first { first = false; if generic_args.has_self_type { @@ -1379,7 +1431,7 @@ impl HirDisplay for Path { } arg.hir_fmt(f)?; } - for binding in &generic_args.bindings { + for binding in generic_args.bindings.iter() { if first { first = false; } else { @@ -1393,7 +1445,7 @@ impl HirDisplay for Path { } None => { write!(f, ": ")?; - f.write_joined(&binding.bounds, " + ")?; + f.write_joined(binding.bounds.iter(), " + ")?; } } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs index 6b59f1c20..767afdf9e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -22,15 +22,15 @@ use hir_def::{ builtin_type::{BuiltinInt, BuiltinType, BuiltinUint}, data::{ConstData, StaticData}, expr::{BindingAnnotation, ExprId, ExprOrPatId, PatId}, - lang_item::LangItemTarget, + lang_item::{LangItem, LangItemTarget}, layout::Integer, - path::{path, Path}, + path::Path, resolver::{HasResolver, ResolveValueResult, Resolver, TypeNs, ValueNs}, type_ref::TypeRef, AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule, ItemContainerId, Lookup, TraitId, TypeAliasId, VariantId, }; -use hir_expand::name::{name, Name}; +use hir_expand::name::name; use itertools::Either; use la_arena::ArenaMap; use rustc_hash::FxHashMap; @@ -39,7 +39,7 @@ use stdx::always; use crate::{ db::HirDatabase, fold_tys, fold_tys_and_consts, infer::coerce::CoerceMany, lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy, Const, DomainGoal, - GenericArg, Goal, ImplTraitId, InEnvironment, Interner, ProjectionTy, Substitution, + GenericArg, Goal, ImplTraitId, InEnvironment, Interner, ProjectionTy, RpitId, Substitution, TraitEnvironment, TraitRef, Ty, TyBuilder, TyExt, TyKind, }; @@ -219,6 +219,7 @@ struct InternedStandardTypes { unknown: Ty, bool_: Ty, unit: Ty, + never: Ty, } impl Default for InternedStandardTypes { @@ -227,6 +228,7 @@ impl Default for InternedStandardTypes { unknown: TyKind::Error.intern(Interner), bool_: TyKind::Scalar(Scalar::Bool).intern(Interner), unit: TyKind::Tuple(0, Substitution::empty(Interner)).intern(Interner), + never: TyKind::Never.intern(Interner), } } } @@ -352,6 +354,7 @@ pub struct InferenceResult { /// **Note**: When a pattern type is resolved it may still contain /// unresolved or missing subpatterns or subpatterns of mismatched types. pub type_of_pat: ArenaMap<PatId, Ty>, + pub type_of_rpit: ArenaMap<RpitId, Ty>, type_mismatches: FxHashMap<ExprOrPatId, TypeMismatch>, /// Interned common types to return references to. standard_types: InternedStandardTypes, @@ -525,6 +528,9 @@ impl<'a> InferenceContext<'a> { for ty in result.type_of_pat.values_mut() { *ty = table.resolve_completely(ty.clone()); } + for ty in result.type_of_rpit.iter_mut().map(|x| x.1) { + *ty = table.resolve_completely(ty.clone()); + } for mismatch in result.type_mismatches.values_mut() { mismatch.expected = table.resolve_completely(mismatch.expected.clone()); mismatch.actual = table.resolve_completely(mismatch.actual.clone()); @@ -603,7 +609,7 @@ impl<'a> InferenceContext<'a> { _ => unreachable!(), }; let bounds = (*rpits).map_ref(|rpits| { - rpits.impl_traits[idx as usize].bounds.map_ref(|it| it.into_iter()) + rpits.impl_traits[idx].bounds.map_ref(|it| it.into_iter()) }); let var = self.table.new_type_var(); let var_subst = Substitution::from1(Interner, var.clone()); @@ -616,6 +622,7 @@ impl<'a> InferenceContext<'a> { always!(binders.is_empty(Interner)); // quantified where clauses not yet handled self.push_obligation(var_predicate.cast(Interner)); } + self.result.type_of_rpit.insert(idx, var.clone()); var }, DebruijnIndex::INNERMOST, @@ -917,104 +924,98 @@ impl<'a> InferenceContext<'a> { } } - fn resolve_lang_item(&self, name: Name) -> Option<LangItemTarget> { + fn resolve_lang_item(&self, item: LangItem) -> Option<LangItemTarget> { let krate = self.resolver.krate(); - self.db.lang_item(krate, name.to_smol_str()) + self.db.lang_item(krate, item) } fn resolve_into_iter_item(&self) -> Option<TypeAliasId> { - let path = path![core::iter::IntoIterator]; - let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?; + let ItemContainerId::TraitId(trait_) = self.resolve_lang_item(LangItem::IntoIterIntoIter)? + .as_function()? + .lookup(self.db.upcast()).container + else { return None }; self.db.trait_data(trait_).associated_type_by_name(&name![IntoIter]) } fn resolve_iterator_item(&self) -> Option<TypeAliasId> { - let path = path![core::iter::Iterator]; - let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?; + let ItemContainerId::TraitId(trait_) = self.resolve_lang_item(LangItem::IteratorNext)? + .as_function()? + .lookup(self.db.upcast()).container + else { return None }; self.db.trait_data(trait_).associated_type_by_name(&name![Item]) } - fn resolve_ops_try_ok(&self) -> Option<TypeAliasId> { - // FIXME resolve via lang_item once try v2 is stable - let path = path![core::ops::Try]; - let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?; - let trait_data = self.db.trait_data(trait_); - trait_data - // FIXME remove once try v2 is stable - .associated_type_by_name(&name![Ok]) - .or_else(|| trait_data.associated_type_by_name(&name![Output])) + fn resolve_output_on(&self, trait_: TraitId) -> Option<TypeAliasId> { + self.db.trait_data(trait_).associated_type_by_name(&name![Output]) + } + + fn resolve_lang_trait(&self, lang: LangItem) -> Option<TraitId> { + self.resolve_lang_item(lang)?.as_trait() + } + + fn resolve_ops_try_output(&self) -> Option<TypeAliasId> { + self.resolve_output_on(self.resolve_lang_trait(LangItem::Try)?) } fn resolve_ops_neg_output(&self) -> Option<TypeAliasId> { - let trait_ = self.resolve_lang_item(name![neg])?.as_trait()?; - self.db.trait_data(trait_).associated_type_by_name(&name![Output]) + self.resolve_output_on(self.resolve_lang_trait(LangItem::Neg)?) } fn resolve_ops_not_output(&self) -> Option<TypeAliasId> { - let trait_ = self.resolve_lang_item(name![not])?.as_trait()?; - self.db.trait_data(trait_).associated_type_by_name(&name![Output]) + self.resolve_output_on(self.resolve_lang_trait(LangItem::Not)?) } fn resolve_future_future_output(&self) -> Option<TypeAliasId> { - let trait_ = self - .resolver - .resolve_known_trait(self.db.upcast(), &path![core::future::IntoFuture]) - .or_else(|| self.resolve_lang_item(name![future_trait])?.as_trait())?; - self.db.trait_data(trait_).associated_type_by_name(&name![Output]) + let ItemContainerId::TraitId(trait_) = self + .resolve_lang_item(LangItem::IntoFutureIntoFuture)? + .as_function()? + .lookup(self.db.upcast()) + .container + else { return None }; + self.resolve_output_on(trait_) } fn resolve_boxed_box(&self) -> Option<AdtId> { - let struct_ = self.resolve_lang_item(name![owned_box])?.as_struct()?; + let struct_ = self.resolve_lang_item(LangItem::OwnedBox)?.as_struct()?; Some(struct_.into()) } fn resolve_range_full(&self) -> Option<AdtId> { - let path = path![core::ops::RangeFull]; - let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; + let struct_ = self.resolve_lang_item(LangItem::RangeFull)?.as_struct()?; Some(struct_.into()) } fn resolve_range(&self) -> Option<AdtId> { - let path = path![core::ops::Range]; - let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; + let struct_ = self.resolve_lang_item(LangItem::Range)?.as_struct()?; Some(struct_.into()) } fn resolve_range_inclusive(&self) -> Option<AdtId> { - let path = path![core::ops::RangeInclusive]; - let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; + let struct_ = self.resolve_lang_item(LangItem::RangeInclusiveStruct)?.as_struct()?; Some(struct_.into()) } fn resolve_range_from(&self) -> Option<AdtId> { - let path = path![core::ops::RangeFrom]; - let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; + let struct_ = self.resolve_lang_item(LangItem::RangeFrom)?.as_struct()?; Some(struct_.into()) } fn resolve_range_to(&self) -> Option<AdtId> { - let path = path![core::ops::RangeTo]; - let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; + let struct_ = self.resolve_lang_item(LangItem::RangeTo)?.as_struct()?; Some(struct_.into()) } fn resolve_range_to_inclusive(&self) -> Option<AdtId> { - let path = path![core::ops::RangeToInclusive]; - let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; + let struct_ = self.resolve_lang_item(LangItem::RangeToInclusive)?.as_struct()?; Some(struct_.into()) } - fn resolve_ops_index(&self) -> Option<TraitId> { - self.resolve_lang_item(name![index])?.as_trait() - } - fn resolve_ops_index_output(&self) -> Option<TypeAliasId> { - let trait_ = self.resolve_ops_index()?; - self.db.trait_data(trait_).associated_type_by_name(&name![Output]) + self.resolve_output_on(self.resolve_lang_trait(LangItem::Index)?) } fn resolve_va_list(&self) -> Option<AdtId> { - let struct_ = self.resolve_lang_item(name![va_list])?.as_struct()?; + let struct_ = self.resolve_lang_item(LangItem::VaList)?.as_struct()?; Some(struct_.into()) } } @@ -1025,7 +1026,8 @@ impl<'a> InferenceContext<'a> { pub(crate) enum Expectation { None, HasType(Ty), - // Castable(Ty), // rustc has this, we currently just don't propagate an expectation for casts + #[allow(dead_code)] + Castable(Ty), RValueLikeUnsized(Ty), } @@ -1041,10 +1043,6 @@ impl Expectation { } } - fn from_option(ty: Option<Ty>) -> Self { - ty.map_or(Expectation::None, Expectation::HasType) - } - /// The following explanation is copied straight from rustc: /// Provides an expectation for an rvalue expression given an *optional* /// hint, which is not required for type safety (the resulting type might @@ -1082,6 +1080,7 @@ impl Expectation { match self { Expectation::None => Expectation::None, Expectation::HasType(t) => Expectation::HasType(table.resolve_ty_shallow(t)), + Expectation::Castable(t) => Expectation::Castable(table.resolve_ty_shallow(t)), Expectation::RValueLikeUnsized(t) => { Expectation::RValueLikeUnsized(table.resolve_ty_shallow(t)) } @@ -1091,20 +1090,25 @@ impl Expectation { fn to_option(&self, table: &mut unify::InferenceTable<'_>) -> Option<Ty> { match self.resolve(table) { Expectation::None => None, - Expectation::HasType(t) | - // Expectation::Castable(t) | - Expectation::RValueLikeUnsized(t) => Some(t), + Expectation::HasType(t) + | Expectation::Castable(t) + | Expectation::RValueLikeUnsized(t) => Some(t), } } fn only_has_type(&self, table: &mut unify::InferenceTable<'_>) -> Option<Ty> { match self { Expectation::HasType(t) => Some(table.resolve_ty_shallow(t)), - // Expectation::Castable(_) | - Expectation::RValueLikeUnsized(_) | Expectation::None => None, + Expectation::Castable(_) | Expectation::RValueLikeUnsized(_) | Expectation::None => { + None + } } } + fn coercion_target_type(&self, table: &mut unify::InferenceTable<'_>) -> Ty { + self.only_has_type(table).unwrap_or_else(|| table.new_type_var()) + } + /// Comment copied from rustc: /// Disregard "castable to" expectations because they /// can lead us astray. Consider for example `if cond diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs index 094e460db..a6449d019 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs @@ -51,7 +51,7 @@ impl InferenceContext<'_> { .map(to_chalk_trait_id) .collect(); - let self_ty = TyKind::Error.intern(Interner); + let self_ty = self.result.standard_types.unknown.clone(); let bounds = dyn_ty.bounds.clone().substitute(Interner, &[self_ty.cast(Interner)]); for bound in bounds.iter(Interner) { // NOTE(skip_binders): the extracted types are rebound by the returned `FnPointer` @@ -67,7 +67,7 @@ impl InferenceContext<'_> { let arg = projection.substitution.as_slice(Interner).get(1)?; if let Some(subst) = arg.ty(Interner)?.as_tuple() { let generic_args = subst.as_slice(Interner); - let mut sig_tys = Vec::new(); + let mut sig_tys = Vec::with_capacity(generic_args.len() + 1); for arg in generic_args { sig_tys.push(arg.ty(Interner)?.clone()); } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs index 8df25c83c..3293534a0 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs @@ -8,9 +8,11 @@ use std::{iter, sync::Arc}; use chalk_ir::{cast::Cast, BoundVar, Goal, Mutability, TyVariableKind}; -use hir_def::{expr::ExprId, lang_item::LangItemTarget}; +use hir_def::{ + expr::ExprId, + lang_item::{LangItem, LangItemTarget}, +}; use stdx::always; -use syntax::SmolStr; use crate::{ autoderef::{Autoderef, AutoderefKind}, @@ -570,11 +572,10 @@ impl<'a> InferenceTable<'a> { reborrow.as_ref().map_or_else(|| from_ty.clone(), |(_, adj)| adj.target.clone()); let krate = self.trait_env.krate; - let coerce_unsized_trait = - match self.db.lang_item(krate, SmolStr::new_inline("coerce_unsized")) { - Some(LangItemTarget::TraitId(trait_)) => trait_, - _ => return Err(TypeError), - }; + let coerce_unsized_trait = match self.db.lang_item(krate, LangItem::CoerceUnsized) { + Some(LangItemTarget::Trait(trait_)) => trait_, + _ => return Err(TypeError), + }; let coerce_unsized_tref = { let b = TyBuilder::trait_ref(self.db, coerce_unsized_trait); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs index 8f9cdac37..175fded8c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs @@ -10,15 +10,15 @@ use chalk_ir::{ }; use hir_def::{ expr::{ - ArithOp, Array, BinaryOp, ClosureKind, CmpOp, Expr, ExprId, LabelId, Literal, Statement, - UnaryOp, + ArithOp, Array, BinaryOp, ClosureKind, Expr, ExprId, LabelId, Literal, Statement, UnaryOp, }, generics::TypeOrConstParamData, + lang_item::LangItem, path::{GenericArg, GenericArgs}, resolver::resolver_for_expr, ConstParamId, FieldId, ItemContainerId, Lookup, }; -use hir_expand::name::Name; +use hir_expand::name::{name, Name}; use stdx::always; use syntax::ast::RangeOp; @@ -30,7 +30,7 @@ use crate::{ const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode, }, mapping::{from_chalk, ToChalk}, - method_resolution::{self, lang_names_for_bin_op, VisibleFromModule}, + method_resolution::{self, lang_items_for_bin_op, VisibleFromModule}, primitive::{self, UintTy}, static_lifetime, to_chalk_trait_id, utils::{generics, Generics}, @@ -87,16 +87,15 @@ impl<'a> InferenceContext<'a> { let expected = &expected.adjust_for_branches(&mut self.table); self.infer_expr( condition, - &Expectation::has_type(TyKind::Scalar(Scalar::Bool).intern(Interner)), + &Expectation::HasType(self.result.standard_types.bool_.clone()), ); let condition_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let mut both_arms_diverge = Diverges::Always; - let result_ty = self.table.new_type_var(); let then_ty = self.infer_expr_inner(then_branch, expected); both_arms_diverge &= mem::replace(&mut self.diverges, Diverges::Maybe); - let mut coerce = CoerceMany::new(result_ty); + let mut coerce = CoerceMany::new(expected.coercion_target_type(&mut self.table)); coerce.coerce(self, Some(then_branch), &then_ty); let else_ty = match else_branch { Some(else_branch) => self.infer_expr_inner(else_branch, expected), @@ -113,7 +112,7 @@ impl<'a> InferenceContext<'a> { &Expr::Let { pat, expr } => { let input_ty = self.infer_expr(expr, &Expectation::none()); self.infer_pat(pat, &input_ty, BindingMode::default()); - TyKind::Scalar(Scalar::Bool).intern(Interner) + self.result.standard_types.bool_.clone() } Expr::Block { statements, tail, label, id: _ } => { let old_resolver = mem::replace( @@ -158,7 +157,8 @@ impl<'a> InferenceContext<'a> { } // The ok-ish type that is expected from the last expression - let ok_ty = self.resolve_associated_type(try_ty.clone(), self.resolve_ops_try_ok()); + let ok_ty = + self.resolve_associated_type(try_ty.clone(), self.resolve_ops_try_output()); self.with_breakable_ctx(BreakableKind::Block, ok_ty.clone(), None, |this| { this.infer_expr(*body, &Expectation::has_type(ok_ty)); @@ -187,10 +187,12 @@ impl<'a> InferenceContext<'a> { .intern(Interner) } &Expr::Loop { body, label } => { + // FIXME: should be: + // let ty = expected.coercion_target_type(&mut self.table); let ty = self.table.new_type_var(); let (breaks, ()) = self.with_breakable_ctx(BreakableKind::Loop, ty, label, |this| { - this.infer_expr(body, &Expectation::has_type(TyBuilder::unit())); + this.infer_expr(body, &Expectation::HasType(TyBuilder::unit())); }); match breaks { @@ -198,16 +200,16 @@ impl<'a> InferenceContext<'a> { self.diverges = Diverges::Maybe; breaks } - None => TyKind::Never.intern(Interner), + None => self.result.standard_types.never.clone(), } } &Expr::While { condition, body, label } => { self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| { this.infer_expr( condition, - &Expectation::has_type(TyKind::Scalar(Scalar::Bool).intern(Interner)), + &Expectation::HasType(this.result.standard_types.bool_.clone()), ); - this.infer_expr(body, &Expectation::has_type(TyBuilder::unit())); + this.infer_expr(body, &Expectation::HasType(TyBuilder::unit())); }); // the body may not run, so it diverging doesn't mean we diverge @@ -223,7 +225,7 @@ impl<'a> InferenceContext<'a> { self.infer_pat(pat, &pat_ty, BindingMode::default()); self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| { - this.infer_expr(body, &Expectation::has_type(TyBuilder::unit())); + this.infer_expr(body, &Expectation::HasType(TyBuilder::unit())); }); // the body may not run, so it diverging doesn't mean we diverge @@ -233,7 +235,7 @@ impl<'a> InferenceContext<'a> { Expr::Closure { body, args, ret_type, arg_types, closure_kind } => { assert_eq!(args.len(), arg_types.len()); - let mut sig_tys = Vec::new(); + let mut sig_tys = Vec::with_capacity(arg_types.len() + 1); // collect explicitly written argument types for arg_type in arg_types.iter() { @@ -254,7 +256,8 @@ impl<'a> InferenceContext<'a> { num_binders: 0, sig: FnSig { abi: (), safety: chalk_ir::Safety::Safe, variadic: false }, substitution: FnSubst( - Substitution::from_iter(Interner, sig_tys.clone()).shifted_in(Interner), + Substitution::from_iter(Interner, sig_tys.iter().cloned()) + .shifted_in(Interner), ), }) .intern(Interner); @@ -316,27 +319,34 @@ impl<'a> InferenceContext<'a> { Expr::Call { callee, args, .. } => { let callee_ty = self.infer_expr(*callee, &Expectation::none()); let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone()); - let mut res = None; - let mut derefed_callee = callee_ty.clone(); - // manual loop to be able to access `derefs.table` - while let Some((callee_deref_ty, _)) = derefs.next() { - res = derefs.table.callable_sig(&callee_deref_ty, args.len()); - if res.is_some() { - derefed_callee = callee_deref_ty; - break; + let (res, derefed_callee) = 'b: { + // manual loop to be able to access `derefs.table` + while let Some((callee_deref_ty, _)) = derefs.next() { + let res = derefs.table.callable_sig(&callee_deref_ty, args.len()); + if res.is_some() { + break 'b (res, callee_deref_ty); + } } - } + (None, callee_ty.clone()) + }; // if the function is unresolved, we use is_varargs=true to // suppress the arg count diagnostic here let is_varargs = derefed_callee.callable_sig(self.db).map_or(false, |sig| sig.is_varargs) || res.is_none(); let (param_tys, ret_ty) = match res { - Some(res) => { + Some((func, params, ret_ty)) => { let adjustments = auto_deref_adjust_steps(&derefs); // FIXME: Handle call adjustments for Fn/FnMut self.write_expr_adj(*callee, adjustments); - res + if let Some((trait_, func)) = func { + let subst = TyBuilder::subst_for_def(self.db, trait_, None) + .push(callee_ty.clone()) + .push(TyBuilder::tuple_with(params.iter().cloned())) + .build(); + self.write_method_resolution(tgt_expr, func, subst.clone()); + } + (params, ret_ty) } None => (Vec::new(), self.err_ty()), // FIXME diagnostic }; @@ -374,12 +384,9 @@ impl<'a> InferenceContext<'a> { let expected = expected.adjust_for_branches(&mut self.table); let result_ty = if arms.is_empty() { - TyKind::Never.intern(Interner) + self.result.standard_types.never.clone() } else { - match &expected { - Expectation::HasType(ty) => ty.clone(), - _ => self.table.new_type_var(), - } + expected.coercion_target_type(&mut self.table) }; let mut coerce = CoerceMany::new(result_ty); @@ -392,7 +399,7 @@ impl<'a> InferenceContext<'a> { if let Some(guard_expr) = arm.guard { self.infer_expr( guard_expr, - &Expectation::has_type(TyKind::Scalar(Scalar::Bool).intern(Interner)), + &Expectation::HasType(self.result.standard_types.bool_.clone()), ); } @@ -417,7 +424,7 @@ impl<'a> InferenceContext<'a> { is_break: false, }); }; - TyKind::Never.intern(Interner) + self.result.standard_types.never.clone() } Expr::Break { expr, label } => { let val_ty = if let Some(expr) = *expr { @@ -431,7 +438,7 @@ impl<'a> InferenceContext<'a> { // avoiding the borrowck let mut coerce = mem::replace( &mut ctxt.coerce, - CoerceMany::new(self.result.standard_types.unknown.clone()), + CoerceMany::new(expected.coercion_target_type(&mut self.table)), ); // FIXME: create a synthetic `()` during lowering so we have something to refer to here? @@ -449,7 +456,7 @@ impl<'a> InferenceContext<'a> { }); } } - TyKind::Never.intern(Interner) + self.result.standard_types.never.clone() } Expr::Return { expr } => { if let Some(expr) = expr { @@ -458,7 +465,7 @@ impl<'a> InferenceContext<'a> { let unit = TyBuilder::unit(); let _ = self.coerce(Some(tgt_expr), &unit, &self.return_ty.clone()); } - TyKind::Never.intern(Interner) + self.result.standard_types.never.clone() } Expr::Yield { expr } => { if let Some((resume_ty, yield_ty)) = self.resume_yield_tys.clone() { @@ -471,14 +478,14 @@ impl<'a> InferenceContext<'a> { resume_ty } else { // FIXME: report error (yield expr in non-generator) - TyKind::Error.intern(Interner) + self.result.standard_types.unknown.clone() } } Expr::Yeet { expr } => { if let &Some(expr) = expr { self.infer_expr_inner(expr, &Expectation::None); } - TyKind::Never.intern(Interner) + self.result.standard_types.never.clone() } Expr::RecordLit { path, fields, spread, .. } => { let (ty, def_id) = self.resolve_variant(path.as_deref(), false); @@ -588,12 +595,23 @@ impl<'a> InferenceContext<'a> { } Expr::Try { expr } => { let inner_ty = self.infer_expr_inner(*expr, &Expectation::none()); - self.resolve_associated_type(inner_ty, self.resolve_ops_try_ok()) + if let Some(trait_) = self.resolve_lang_trait(LangItem::Try) { + if let Some(func) = self.db.trait_data(trait_).method_by_name(&name!(branch)) { + let subst = TyBuilder::subst_for_def(self.db, trait_, None) + .push(inner_ty.clone()) + .build(); + self.write_method_resolution(tgt_expr, func, subst.clone()); + } + let try_output = self.resolve_output_on(trait_); + self.resolve_associated_type(inner_ty, try_output) + } else { + self.err_ty() + } } Expr::Cast { expr, type_ref } => { - // FIXME: propagate the "castable to" expectation (and find a test case that shows this is necessary) - let _inner_ty = self.infer_expr_inner(*expr, &Expectation::none()); let cast_ty = self.make_ty(type_ref); + // FIXME: propagate the "castable to" expectation + let _inner_ty = self.infer_expr_inner(*expr, &Expectation::None); // FIXME check the cast... cast_ty } @@ -627,6 +645,7 @@ impl<'a> InferenceContext<'a> { Expr::UnaryOp { expr, op } => { let inner_ty = self.infer_expr_inner(*expr, &Expectation::none()); let inner_ty = self.resolve_ty_shallow(&inner_ty); + // FIXME: Note down method resolution her match op { UnaryOp::Deref => { autoderef::deref(&mut self.table, inner_ty).unwrap_or_else(|| self.err_ty()) @@ -736,7 +755,7 @@ impl<'a> InferenceContext<'a> { let base_ty = self.infer_expr_inner(*base, &Expectation::none()); let index_ty = self.infer_expr(*index, &Expectation::none()); - if let Some(index_trait) = self.resolve_ops_index() { + if let Some(index_trait) = self.resolve_lang_trait(LangItem::Index) { let canonicalized = self.canonicalize(base_ty.clone()); let receiver_adjustments = method_resolution::resolve_indexing_op( self.db, @@ -749,6 +768,15 @@ impl<'a> InferenceContext<'a> { adj.apply(&mut self.table, base_ty) }); self.write_expr_adj(*base, adj); + if let Some(func) = + self.db.trait_data(index_trait).method_by_name(&name!(index)) + { + let substs = TyBuilder::subst_for_def(self.db, index_trait, None) + .push(self_ty.clone()) + .push(index_ty.clone()) + .build(); + self.write_method_resolution(tgt_expr, func, substs.clone()); + } self.resolve_associated_type_with_params( self_ty, self.resolve_ops_index_output(), @@ -800,7 +828,7 @@ impl<'a> InferenceContext<'a> { self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty)); self.infer_expr( repeat, - &Expectation::has_type( + &Expectation::HasType( TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner), ), ); @@ -823,7 +851,7 @@ impl<'a> InferenceContext<'a> { TyKind::Array(coerce.complete(), len).intern(Interner) } Expr::Literal(lit) => match lit { - Literal::Bool(..) => TyKind::Scalar(Scalar::Bool).intern(Interner), + Literal::Bool(..) => self.result.standard_types.bool_.clone(), Literal::String(..) => { TyKind::Ref(Mutability::Not, static_lifetime(), TyKind::Str.intern(Interner)) .intern(Interner) @@ -1009,7 +1037,7 @@ impl<'a> InferenceContext<'a> { let lhs_ty = self.infer_expr(lhs, &lhs_expectation); let rhs_ty = self.table.new_type_var(); - let trait_func = lang_names_for_bin_op(op).and_then(|(name, lang_item)| { + let trait_func = lang_items_for_bin_op(op).and_then(|(name, lang_item)| { let trait_id = self.resolve_lang_item(lang_item)?.as_trait()?; let func = self.db.trait_data(trait_id).method_by_name(&name)?; Some((trait_id, func)) @@ -1017,11 +1045,21 @@ impl<'a> InferenceContext<'a> { let (trait_, func) = match trait_func { Some(it) => it, None => { - let rhs_ty = self.builtin_binary_op_rhs_expectation(op, lhs_ty.clone()); - let rhs_ty = self.infer_expr_coerce(rhs, &Expectation::from_option(rhs_ty)); - return self - .builtin_binary_op_return_ty(op, lhs_ty, rhs_ty) - .unwrap_or_else(|| self.err_ty()); + // HACK: `rhs_ty` is a general inference variable with no clue at all at this + // point. Passing `lhs_ty` as both operands just to check if `lhs_ty` is a builtin + // type applicable to `op`. + let ret_ty = if self.is_builtin_binop(&lhs_ty, &lhs_ty, op) { + // Assume both operands are builtin so we can continue inference. No guarantee + // on the correctness, rustc would complain as necessary lang items don't seem + // to exist anyway. + self.enforce_builtin_binop_types(&lhs_ty, &rhs_ty, op) + } else { + self.err_ty() + }; + + self.infer_expr_coerce(rhs, &Expectation::has_type(rhs_ty)); + + return ret_ty; } }; @@ -1071,11 +1109,9 @@ impl<'a> InferenceContext<'a> { let ret_ty = self.normalize_associated_types_in(ret_ty); - // use knowledge of built-in binary ops, which can sometimes help inference - if let Some(builtin_rhs) = self.builtin_binary_op_rhs_expectation(op, lhs_ty.clone()) { - self.unify(&builtin_rhs, &rhs_ty); - } - if let Some(builtin_ret) = self.builtin_binary_op_return_ty(op, lhs_ty, rhs_ty) { + if self.is_builtin_binop(&lhs_ty, &rhs_ty, op) { + // use knowledge of built-in binary ops, which can sometimes help inference + let builtin_ret = self.enforce_builtin_binop_types(&lhs_ty, &rhs_ty, op); self.unify(&builtin_ret, &ret_ty); } @@ -1111,7 +1147,7 @@ impl<'a> InferenceContext<'a> { if let Some(expr) = else_branch { self.infer_expr_coerce( *expr, - &Expectation::has_type(Ty::new(Interner, TyKind::Never)), + &Expectation::HasType(self.result.standard_types.never.clone()), ); } @@ -1136,18 +1172,16 @@ impl<'a> InferenceContext<'a> { if self.diverges.is_always() { // we don't even make an attempt at coercion self.table.new_maybe_never_var() - } else { - if let Some(t) = expected.only_has_type(&mut self.table) { - if self.coerce(Some(expr), &TyBuilder::unit(), &t).is_err() { - self.result.type_mismatches.insert( - expr.into(), - TypeMismatch { expected: t.clone(), actual: TyBuilder::unit() }, - ); - } - t - } else { - TyBuilder::unit() + } else if let Some(t) = expected.only_has_type(&mut self.table) { + if self.coerce(Some(expr), &TyBuilder::unit(), &t).is_err() { + self.result.type_mismatches.insert( + expr.into(), + TypeMismatch { expected: t.clone(), actual: TyBuilder::unit() }, + ); } + t + } else { + TyBuilder::unit() } } } @@ -1271,7 +1305,7 @@ impl<'a> InferenceContext<'a> { // that are not closures, then we type-check the closures. This is so // that we have more information about the types of arguments when we // type-check the functions. This isn't really the right way to do this. - for &check_closures in &[false, true] { + for check_closures in [false, true] { let mut skip_indices = skip_indices.into_iter().copied().fuse().peekable(); let param_iter = param_tys.iter().cloned().chain(repeat(self.err_ty())); let expected_iter = expected_inputs @@ -1314,13 +1348,13 @@ impl<'a> InferenceContext<'a> { } else { param_ty }; - if !coercion_target.is_unknown() { - if self.coerce(Some(arg), &ty, &coercion_target).is_err() { - self.result.type_mismatches.insert( - arg.into(), - TypeMismatch { expected: coercion_target, actual: ty.clone() }, - ); - } + if !coercion_target.is_unknown() + && self.coerce(Some(arg), &ty, &coercion_target).is_err() + { + self.result.type_mismatches.insert( + arg.into(), + TypeMismatch { expected: coercion_target, actual: ty.clone() }, + ); } } } @@ -1479,92 +1513,124 @@ impl<'a> InferenceContext<'a> { indices } - fn builtin_binary_op_return_ty(&mut self, op: BinaryOp, lhs_ty: Ty, rhs_ty: Ty) -> Option<Ty> { - let lhs_ty = self.resolve_ty_shallow(&lhs_ty); - let rhs_ty = self.resolve_ty_shallow(&rhs_ty); - match op { - BinaryOp::LogicOp(_) | BinaryOp::CmpOp(_) => { - Some(TyKind::Scalar(Scalar::Bool).intern(Interner)) + /// Dereferences a single level of immutable referencing. + fn deref_ty_if_possible(&mut self, ty: &Ty) -> Ty { + let ty = self.resolve_ty_shallow(ty); + match ty.kind(Interner) { + TyKind::Ref(Mutability::Not, _, inner) => self.resolve_ty_shallow(inner), + _ => ty, + } + } + + /// Enforces expectations on lhs type and rhs type depending on the operator and returns the + /// output type of the binary op. + fn enforce_builtin_binop_types(&mut self, lhs: &Ty, rhs: &Ty, op: BinaryOp) -> Ty { + // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work (See rust-lang/rust#57447). + let lhs = self.deref_ty_if_possible(lhs); + let rhs = self.deref_ty_if_possible(rhs); + + let (op, is_assign) = match op { + BinaryOp::Assignment { op: Some(inner) } => (BinaryOp::ArithOp(inner), true), + _ => (op, false), + }; + + let output_ty = match op { + BinaryOp::LogicOp(_) => { + let bool_ = self.result.standard_types.bool_.clone(); + self.unify(&lhs, &bool_); + self.unify(&rhs, &bool_); + bool_ } - BinaryOp::Assignment { .. } => Some(TyBuilder::unit()), + BinaryOp::ArithOp(ArithOp::Shl | ArithOp::Shr) => { - // all integer combinations are valid here - if matches!( - lhs_ty.kind(Interner), - TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_)) - | TyKind::InferenceVar(_, TyVariableKind::Integer) - ) && matches!( - rhs_ty.kind(Interner), - TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_)) - | TyKind::InferenceVar(_, TyVariableKind::Integer) - ) { - Some(lhs_ty) - } else { - None - } + // result type is same as LHS always + lhs } - BinaryOp::ArithOp(_) => match (lhs_ty.kind(Interner), rhs_ty.kind(Interner)) { - // (int, int) | (uint, uint) | (float, float) - (TyKind::Scalar(Scalar::Int(_)), TyKind::Scalar(Scalar::Int(_))) - | (TyKind::Scalar(Scalar::Uint(_)), TyKind::Scalar(Scalar::Uint(_))) - | (TyKind::Scalar(Scalar::Float(_)), TyKind::Scalar(Scalar::Float(_))) => { - Some(rhs_ty) - } - // ({int}, int) | ({int}, uint) - ( - TyKind::InferenceVar(_, TyVariableKind::Integer), - TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_)), - ) => Some(rhs_ty), - // (int, {int}) | (uint, {int}) - ( - TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_)), - TyKind::InferenceVar(_, TyVariableKind::Integer), - ) => Some(lhs_ty), - // ({float} | float) - ( - TyKind::InferenceVar(_, TyVariableKind::Float), - TyKind::Scalar(Scalar::Float(_)), - ) => Some(rhs_ty), - // (float, {float}) - ( - TyKind::Scalar(Scalar::Float(_)), - TyKind::InferenceVar(_, TyVariableKind::Float), - ) => Some(lhs_ty), - // ({int}, {int}) | ({float}, {float}) - ( - TyKind::InferenceVar(_, TyVariableKind::Integer), - TyKind::InferenceVar(_, TyVariableKind::Integer), - ) - | ( - TyKind::InferenceVar(_, TyVariableKind::Float), - TyKind::InferenceVar(_, TyVariableKind::Float), - ) => Some(rhs_ty), - _ => None, - }, + + BinaryOp::ArithOp(_) => { + // LHS, RHS, and result will have the same type + self.unify(&lhs, &rhs); + lhs + } + + BinaryOp::CmpOp(_) => { + // LHS and RHS will have the same type + self.unify(&lhs, &rhs); + self.result.standard_types.bool_.clone() + } + + BinaryOp::Assignment { op: None } => { + stdx::never!("Simple assignment operator is not binary op."); + lhs + } + + BinaryOp::Assignment { .. } => unreachable!("handled above"), + }; + + if is_assign { + self.result.standard_types.unit.clone() + } else { + output_ty } } - fn builtin_binary_op_rhs_expectation(&mut self, op: BinaryOp, lhs_ty: Ty) -> Option<Ty> { - Some(match op { - BinaryOp::LogicOp(..) => TyKind::Scalar(Scalar::Bool).intern(Interner), - BinaryOp::Assignment { op: None } => lhs_ty, - BinaryOp::CmpOp(CmpOp::Eq { .. }) => match self - .resolve_ty_shallow(&lhs_ty) - .kind(Interner) - { - TyKind::Scalar(_) | TyKind::Str => lhs_ty, - TyKind::InferenceVar(_, TyVariableKind::Integer | TyVariableKind::Float) => lhs_ty, - _ => return None, - }, - BinaryOp::ArithOp(ArithOp::Shl | ArithOp::Shr) => return None, - BinaryOp::CmpOp(CmpOp::Ord { .. }) - | BinaryOp::Assignment { op: Some(_) } - | BinaryOp::ArithOp(_) => match self.resolve_ty_shallow(&lhs_ty).kind(Interner) { - TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_) | Scalar::Float(_)) => lhs_ty, - TyKind::InferenceVar(_, TyVariableKind::Integer | TyVariableKind::Float) => lhs_ty, - _ => return None, - }, - }) + fn is_builtin_binop(&mut self, lhs: &Ty, rhs: &Ty, op: BinaryOp) -> bool { + // Special-case a single layer of referencing, so that things like `5.0 + &6.0f32` work (See rust-lang/rust#57447). + let lhs = self.deref_ty_if_possible(lhs); + let rhs = self.deref_ty_if_possible(rhs); + + let op = match op { + BinaryOp::Assignment { op: Some(inner) } => BinaryOp::ArithOp(inner), + _ => op, + }; + + match op { + BinaryOp::LogicOp(_) => true, + + BinaryOp::ArithOp(ArithOp::Shl | ArithOp::Shr) => { + lhs.is_integral() && rhs.is_integral() + } + + BinaryOp::ArithOp( + ArithOp::Add | ArithOp::Sub | ArithOp::Mul | ArithOp::Div | ArithOp::Rem, + ) => { + lhs.is_integral() && rhs.is_integral() + || lhs.is_floating_point() && rhs.is_floating_point() + } + + BinaryOp::ArithOp(ArithOp::BitAnd | ArithOp::BitOr | ArithOp::BitXor) => { + lhs.is_integral() && rhs.is_integral() + || lhs.is_floating_point() && rhs.is_floating_point() + || matches!( + (lhs.kind(Interner), rhs.kind(Interner)), + (TyKind::Scalar(Scalar::Bool), TyKind::Scalar(Scalar::Bool)) + ) + } + + BinaryOp::CmpOp(_) => { + let is_scalar = |kind| { + matches!( + kind, + &TyKind::Scalar(_) + | TyKind::FnDef(..) + | TyKind::Function(_) + | TyKind::Raw(..) + | TyKind::InferenceVar( + _, + TyVariableKind::Integer | TyVariableKind::Float + ) + ) + }; + is_scalar(lhs.kind(Interner)) && is_scalar(rhs.kind(Interner)) + } + + BinaryOp::Assignment { op: None } => { + stdx::never!("Simple assignment operator is not binary op."); + false + } + + BinaryOp::Assignment { .. } => unreachable!("handled above"), + } } fn with_breakable_ctx<T>( diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs index 8bd17c0f3..0a8527afb 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs @@ -112,7 +112,7 @@ impl<'a> InferenceContext<'a> { let ty = TyBuilder::value_ty(self.db, typable, parent_substs) .fill(|x| { it.next().unwrap_or_else(|| match x { - ParamKind::Type => TyKind::Error.intern(Interner).cast(Interner), + ParamKind::Type => self.result.standard_types.unknown.clone().cast(Interner), ParamKind::Const(ty) => consteval::unknown_const_as_generic(ty.clone()), }) }) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs index e7ddd1591..46ed3533c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs @@ -8,6 +8,7 @@ use chalk_ir::{ }; use chalk_solve::infer::ParameterEnaVariableExt; use ena::unify::UnifyKey; +use hir_def::{FunctionId, TraitId}; use hir_expand::name; use stdx::never; @@ -626,18 +627,26 @@ impl<'a> InferenceTable<'a> { } } - pub(crate) fn callable_sig(&mut self, ty: &Ty, num_args: usize) -> Option<(Vec<Ty>, Ty)> { + pub(crate) fn callable_sig( + &mut self, + ty: &Ty, + num_args: usize, + ) -> Option<(Option<(TraitId, FunctionId)>, Vec<Ty>, Ty)> { match ty.callable_sig(self.db) { - Some(sig) => Some((sig.params().to_vec(), sig.ret().clone())), + Some(sig) => Some((None, sig.params().to_vec(), sig.ret().clone())), None => self.callable_sig_from_fn_trait(ty, num_args), } } - fn callable_sig_from_fn_trait(&mut self, ty: &Ty, num_args: usize) -> Option<(Vec<Ty>, Ty)> { + fn callable_sig_from_fn_trait( + &mut self, + ty: &Ty, + num_args: usize, + ) -> Option<(Option<(TraitId, FunctionId)>, Vec<Ty>, Ty)> { let krate = self.trait_env.krate; let fn_once_trait = FnTrait::FnOnce.get_id(self.db, krate)?; - let output_assoc_type = - self.db.trait_data(fn_once_trait).associated_type_by_name(&name![Output])?; + let trait_data = self.db.trait_data(fn_once_trait); + let output_assoc_type = trait_data.associated_type_by_name(&name![Output])?; let mut arg_tys = vec![]; let arg_ty = TyBuilder::tuple(num_args) @@ -675,7 +684,11 @@ impl<'a> InferenceTable<'a> { if self.db.trait_solve(krate, canonical.value.cast(Interner)).is_some() { self.register_obligation(obligation.goal); let return_ty = self.normalize_projection_ty(projection); - Some((arg_tys, return_ty)) + Some(( + Some(fn_once_trait).zip(trait_data.method_by_name(&name!(call_once))), + arg_tys, + return_ty, + )) } else { None } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs b/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs index 441503a30..7bf73560c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs @@ -4,11 +4,8 @@ use crate::{chalk_db, tls, GenericArg}; use base_db::salsa::InternId; use chalk_ir::{Goal, GoalData}; -use hir_def::{ - intern::{impl_internable, InternStorage, Internable, Interned}, - type_ref::ConstScalar, - TypeAliasId, -}; +use hir_def::{type_ref::ConstScalar, TypeAliasId}; +use intern::{impl_internable, Interned}; use smallvec::SmallVec; use std::{fmt, sync::Arc}; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lang_items.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lang_items.rs index afc54e729..5308c7216 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lang_items.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lang_items.rs @@ -1,20 +1,19 @@ //! Functions to detect special lang items -use hir_def::{AdtId, HasModule}; -use hir_expand::name; +use hir_def::{lang_item::LangItem, AdtId, HasModule}; use crate::db::HirDatabase; pub fn is_box(adt: AdtId, db: &dyn HirDatabase) -> bool { - let owned_box = name![owned_box].to_smol_str(); let krate = adt.module(db.upcast()).krate(); - let box_adt = db.lang_item(krate, owned_box).and_then(|it| it.as_struct()).map(AdtId::from); + let box_adt = + db.lang_item(krate, LangItem::OwnedBox).and_then(|it| it.as_struct()).map(AdtId::from); Some(adt) == box_adt } pub fn is_unsafe_cell(adt: AdtId, db: &dyn HirDatabase) -> bool { - let owned_box = name![unsafe_cell].to_smol_str(); let krate = adt.module(db.upcast()).krate(); - let box_adt = db.lang_item(krate, owned_box).and_then(|it| it.as_struct()).map(AdtId::from); + let box_adt = + db.lang_item(krate, LangItem::UnsafeCell).and_then(|it| it.as_struct()).map(AdtId::from); Some(adt) == box_adt } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs index 7a1cca314..f21b4f84c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs @@ -1,7 +1,5 @@ //! Compute the binary representation of a type -use std::sync::Arc; - use base_db::CrateId; use chalk_ir::{AdtId, TyKind}; use hir_def::{ @@ -31,19 +29,19 @@ mod adt; mod target; struct LayoutCx<'a> { - db: &'a dyn HirDatabase, krate: CrateId, + target: &'a TargetDataLayout, } -impl LayoutCalculator for LayoutCx<'_> { - type TargetDataLayoutRef = Arc<TargetDataLayout>; +impl<'a> LayoutCalculator for LayoutCx<'a> { + type TargetDataLayoutRef = &'a TargetDataLayout; fn delay_bug(&self, txt: &str) { never!("{}", txt); } - fn current_data_layout(&self) -> Arc<TargetDataLayout> { - self.db.target_data_layout(self.krate) + fn current_data_layout(&self) -> &'a TargetDataLayout { + self.target } } @@ -56,7 +54,8 @@ fn scalar(dl: &TargetDataLayout, value: Primitive) -> Layout { } pub fn layout_of_ty(db: &dyn HirDatabase, ty: &Ty, krate: CrateId) -> Result<Layout, LayoutError> { - let cx = LayoutCx { db, krate }; + let Some(target) = db.target_data_layout(krate) else { return Err(LayoutError::TargetLayoutNotAvailable) }; + let cx = LayoutCx { krate, target: &target }; let dl = &*cx.current_data_layout(); Ok(match ty.kind(Interner) { TyKind::Adt(AdtId(def), subst) => db.layout_of_adt(*def, subst.clone())?, @@ -174,7 +173,7 @@ pub fn layout_of_ty(db: &dyn HirDatabase, ty: &Ty, krate: CrateId) -> Result<Lay // let pointee = tcx.normalize_erasing_regions(param_env, pointee); // if pointee.is_sized(tcx.at(DUMMY_SP), param_env) { - // return Ok(tcx.intern_layout(LayoutS::scalar(cx, data_ptr))); + // return Ok(tcx.mk_layout(LayoutS::scalar(cx, data_ptr))); // } let unsized_part = struct_tail_erasing_lifetimes(db, pointee.clone()); @@ -226,10 +225,21 @@ pub fn layout_of_ty(db: &dyn HirDatabase, ty: &Ty, krate: CrateId) -> Result<Lay ptr.valid_range_mut().start = 1; Layout::scalar(dl, ptr) } - TyKind::Closure(_, _) - | TyKind::OpaqueType(_, _) - | TyKind::Generator(_, _) - | TyKind::GeneratorWitness(_, _) => return Err(LayoutError::NotImplemented), + TyKind::OpaqueType(opaque_ty_id, _) => { + let impl_trait_id = db.lookup_intern_impl_trait_id((*opaque_ty_id).into()); + match impl_trait_id { + crate::ImplTraitId::ReturnTypeImplTrait(func, idx) => { + let infer = db.infer(func.into()); + layout_of_ty(db, &infer.type_of_rpit[idx], krate)? + } + crate::ImplTraitId::AsyncBlockTypeImplTrait(_, _) => { + return Err(LayoutError::NotImplemented) + } + } + } + TyKind::Closure(_, _) | TyKind::Generator(_, _) | TyKind::GeneratorWitness(_, _) => { + return Err(LayoutError::NotImplemented) + } TyKind::AssociatedType(_, _) | TyKind::Error | TyKind::Alias(_) @@ -251,17 +261,14 @@ fn layout_of_unit(cx: &LayoutCx<'_>, dl: &TargetDataLayout) -> Result<Layout, La fn struct_tail_erasing_lifetimes(db: &dyn HirDatabase, pointee: Ty) -> Ty { match pointee.kind(Interner) { - TyKind::Adt(AdtId(adt), subst) => match adt { - &hir_def::AdtId::StructId(i) => { - let data = db.struct_data(i); - let mut it = data.variant_data.fields().iter().rev(); - match it.next() { - Some((f, _)) => field_ty(db, i.into(), f, subst), - None => pointee, - } + TyKind::Adt(AdtId(hir_def::AdtId::StructId(i)), subst) => { + let data = db.struct_data(*i); + let mut it = data.variant_data.fields().iter().rev(); + match it.next() { + Some((f, _)) => field_ty(db, (*i).into(), f, subst), + None => pointee, } - _ => pointee, - }, + } _ => pointee, } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs index 23166a5a5..cb7968c14 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs @@ -23,7 +23,9 @@ pub fn layout_of_adt_query( def: AdtId, subst: Substitution, ) -> Result<Layout, LayoutError> { - let cx = LayoutCx { db, krate: def.module(db.upcast()).krate() }; + let krate = def.module(db.upcast()).krate(); + let Some(target) = db.target_data_layout(krate) else { return Err(LayoutError::TargetLayoutNotAvailable) }; + let cx = LayoutCx { krate, target: &target }; let dl = cx.current_data_layout(); let handle_variant = |def: VariantId, var: &VariantData| { var.fields() diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/target.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/target.rs index 37b831652..adfae0a1a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/target.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/target.rs @@ -3,34 +3,22 @@ use std::sync::Arc; use base_db::CrateId; -use hir_def::layout::{Endian, Size, TargetDataLayout}; +use hir_def::layout::TargetDataLayout; use crate::db::HirDatabase; -pub fn target_data_layout_query(db: &dyn HirDatabase, krate: CrateId) -> Arc<TargetDataLayout> { +pub fn target_data_layout_query( + db: &dyn HirDatabase, + krate: CrateId, +) -> Option<Arc<TargetDataLayout>> { let crate_graph = db.crate_graph(); - let target_layout = &crate_graph[krate].target_layout; - let cfg_options = &crate_graph[krate].cfg_options; - Arc::new( - target_layout - .as_ref() - .and_then(|it| TargetDataLayout::parse_from_llvm_datalayout_string(it).ok()) - .unwrap_or_else(|| { - let endian = match cfg_options.get_cfg_values("target_endian").next() { - Some(x) if x.as_str() == "big" => Endian::Big, - _ => Endian::Little, - }; - let pointer_size = Size::from_bytes( - match cfg_options.get_cfg_values("target_pointer_width").next() { - Some(x) => match x.as_str() { - "16" => 2, - "32" => 4, - _ => 8, - }, - _ => 8, - }, - ); - TargetDataLayout { endian, pointer_size, ..TargetDataLayout::default() } - }), - ) + let target_layout = crate_graph[krate].target_layout.as_ref().ok()?; + let res = TargetDataLayout::parse_from_llvm_datalayout_string(&target_layout); + if let Err(_e) = &res { + // FIXME: Print the error here once it implements debug/display + // also logging here is somewhat wrong, but unfortunately this is the earliest place we can + // parse that doesn't impose a dependency to the rust-abi crate for project-model + tracing::error!("Failed to parse target data layout for {krate:?}"); + } + res.ok().map(Arc::new) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs index 53838cf41..067bdc960 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use base_db::fixture::WithFixture; use chalk_ir::{AdtId, TyKind}; use hir_def::{ @@ -5,20 +7,16 @@ use hir_def::{ layout::{Layout, LayoutError}, }; -use crate::{test_db::TestDB, Interner, Substitution}; +use crate::{db::HirDatabase, test_db::TestDB, Interner, Substitution}; use super::layout_of_ty; -fn eval_goal(ra_fixture: &str, minicore: &str) -> Result<Layout, LayoutError> { - // using unstable cargo features failed, fall back to using plain rustc - let mut cmd = std::process::Command::new("rustc"); - cmd.args(["-Z", "unstable-options", "--print", "target-spec-json"]).env("RUSTC_BOOTSTRAP", "1"); - let output = cmd.output().unwrap(); - assert!(output.status.success(), "{}", output.status); - let stdout = String::from_utf8(output.stdout).unwrap(); - let target_data_layout = - stdout.split_once(r#""data-layout": ""#).unwrap().1.split_once('"').unwrap().0.to_owned(); +fn current_machine_data_layout() -> String { + project_model::target_data_layout::get(None, None, &HashMap::default()).unwrap() +} +fn eval_goal(ra_fixture: &str, minicore: &str) -> Result<Layout, LayoutError> { + let target_data_layout = current_machine_data_layout(); let ra_fixture = format!( "{minicore}//- /main.rs crate:test target_data_layout:{target_data_layout}\n{ra_fixture}", ); @@ -45,6 +43,42 @@ fn eval_goal(ra_fixture: &str, minicore: &str) -> Result<Layout, LayoutError> { layout_of_ty(&db, &goal_ty, module_id.krate()) } +/// A version of `eval_goal` for types that can not be expressed in ADTs, like closures and `impl Trait` +fn eval_expr(ra_fixture: &str, minicore: &str) -> Result<Layout, LayoutError> { + let target_data_layout = current_machine_data_layout(); + let ra_fixture = format!( + "{minicore}//- /main.rs crate:test target_data_layout:{target_data_layout}\nfn main(){{let goal = {{{ra_fixture}}};}}", + ); + + let (db, file_id) = TestDB::with_single_file(&ra_fixture); + let module_id = db.module_for_file(file_id); + let def_map = module_id.def_map(&db); + let scope = &def_map[module_id.local_id].scope; + let adt_id = scope + .declarations() + .find_map(|x| match x { + hir_def::ModuleDefId::FunctionId(x) => { + let name = db.function_data(x).name.to_smol_str(); + (name == "main").then_some(x) + } + _ => None, + }) + .unwrap(); + let hir_body = db.body(adt_id.into()); + let pat = hir_body + .pats + .iter() + .find(|x| match x.1 { + hir_def::expr::Pat::Bind { name, .. } => name.to_smol_str() == "goal", + _ => false, + }) + .unwrap() + .0; + let infer = db.infer(adt_id.into()); + let goal_ty = infer.type_of_pat[pat].clone(); + layout_of_ty(&db, &goal_ty, module_id.krate()) +} + #[track_caller] fn check_size_and_align(ra_fixture: &str, minicore: &str, size: u64, align: u64) { let l = eval_goal(ra_fixture, minicore).unwrap(); @@ -53,6 +87,13 @@ fn check_size_and_align(ra_fixture: &str, minicore: &str, size: u64, align: u64) } #[track_caller] +fn check_size_and_align_expr(ra_fixture: &str, minicore: &str, size: u64, align: u64) { + let l = eval_expr(ra_fixture, minicore).unwrap(); + assert_eq!(l.size.bytes(), size); + assert_eq!(l.align.abi.bytes(), align); +} + +#[track_caller] fn check_fail(ra_fixture: &str, e: LayoutError) { let r = eval_goal(ra_fixture, ""); assert_eq!(r, Err(e)); @@ -85,11 +126,31 @@ macro_rules! size_and_align { }; } +macro_rules! size_and_align_expr { + ($($t:tt)*) => { + { + #[allow(dead_code)] + { + let val = { $($t)* }; + check_size_and_align_expr( + stringify!($($t)*), + "", + ::std::mem::size_of_val(&val) as u64, + ::std::mem::align_of_val(&val) as u64, + ); + } + } + }; +} + #[test] fn hello_world() { size_and_align! { struct Goal(i32); } + size_and_align_expr! { + 2i32 + } } #[test] @@ -144,6 +205,40 @@ fn generic() { } #[test] +fn return_position_impl_trait() { + size_and_align_expr! { + trait T {} + impl T for i32 {} + impl T for i64 {} + fn foo() -> impl T { 2i64 } + foo() + } + size_and_align_expr! { + trait T {} + impl T for i32 {} + impl T for i64 {} + fn foo() -> (impl T, impl T, impl T) { (2i64, 5i32, 7i32) } + foo() + } + size_and_align_expr! { + struct Foo<T>(T, T, (T, T)); + trait T {} + impl T for Foo<i32> {} + impl T for Foo<i64> {} + + fn foo() -> Foo<impl T> { Foo( + Foo(1i64, 2, (3, 4)), + Foo(5, 6, (7, 8)), + ( + Foo(1i64, 2, (3, 4)), + Foo(5, 6, (7, 8)), + ), + ) } + foo() + } +} + +#[test] fn enums() { size_and_align! { enum Goal { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs index cbe6873c7..59a5ef8c1 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -20,7 +20,6 @@ mod lower; mod mapping; mod tls; mod utils; -mod walk; pub mod db; pub mod diagnostics; pub mod display; @@ -40,11 +39,14 @@ use std::sync::Arc; use chalk_ir::{ fold::{Shift, TypeFoldable}, interner::HasInterner, - NoSolution, + visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor}, + NoSolution, TyData, }; use hir_def::{expr::ExprId, type_ref::Rawness, TypeOrConstParamId}; use hir_expand::name; use itertools::Either; +use la_arena::{Arena, Idx}; +use rustc_hash::FxHashSet; use traits::FnTrait; use utils::Generics; @@ -71,7 +73,6 @@ pub use mapping::{ }; pub use traits::TraitEnvironment; pub use utils::{all_super_traits, is_fn_unsafe_to_call}; -pub use walk::TypeWalk; pub use chalk_ir::{ cast::Cast, AdtId, BoundVar, DebruijnIndex, Mutability, Safety, Scalar, TyVariableKind, @@ -107,6 +108,7 @@ pub type GenericArgData = chalk_ir::GenericArgData<Interner>; pub type Ty = chalk_ir::Ty<Interner>; pub type TyKind = chalk_ir::TyKind<Interner>; +pub type TypeFlags = chalk_ir::TypeFlags; pub type DynTy = chalk_ir::DynTy<Interner>; pub type FnPointer = chalk_ir::FnPointer<Interner>; // pub type FnSubst = chalk_ir::FnSubst<Interner>; @@ -289,22 +291,24 @@ impl TypeFoldable<Interner> for CallableSig { #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub enum ImplTraitId { - ReturnTypeImplTrait(hir_def::FunctionId, u16), + ReturnTypeImplTrait(hir_def::FunctionId, RpitId), AsyncBlockTypeImplTrait(hir_def::DefWithBodyId, ExprId), } #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub struct ReturnTypeImplTraits { - pub(crate) impl_traits: Vec<ReturnTypeImplTrait>, + pub(crate) impl_traits: Arena<ReturnTypeImplTrait>, } has_interner!(ReturnTypeImplTraits); #[derive(Clone, PartialEq, Eq, Debug, Hash)] -pub(crate) struct ReturnTypeImplTrait { +pub struct ReturnTypeImplTrait { pub(crate) bounds: Binders<Vec<QuantifiedWhereClause>>, } +pub type RpitId = Idx<ReturnTypeImplTrait>; + pub fn static_lifetime() -> Lifetime { LifetimeData::Static.intern(Interner) } @@ -563,3 +567,68 @@ pub fn callable_sig_from_fnonce( Some(CallableSig::from_params_and_return(params, ret_ty, false, Safety::Safe)) } + +struct PlaceholderCollector<'db> { + db: &'db dyn HirDatabase, + placeholders: FxHashSet<TypeOrConstParamId>, +} + +impl PlaceholderCollector<'_> { + fn collect(&mut self, idx: PlaceholderIndex) { + let id = from_placeholder_idx(self.db, idx); + self.placeholders.insert(id); + } +} + +impl TypeVisitor<Interner> for PlaceholderCollector<'_> { + type BreakTy = (); + + fn as_dyn(&mut self) -> &mut dyn TypeVisitor<Interner, BreakTy = Self::BreakTy> { + self + } + + fn interner(&self) -> Interner { + Interner + } + + fn visit_ty( + &mut self, + ty: &Ty, + outer_binder: DebruijnIndex, + ) -> std::ops::ControlFlow<Self::BreakTy> { + let has_placeholder_bits = TypeFlags::HAS_TY_PLACEHOLDER | TypeFlags::HAS_CT_PLACEHOLDER; + let TyData { kind, flags } = ty.data(Interner); + + if let TyKind::Placeholder(idx) = kind { + self.collect(*idx); + } else if flags.intersects(has_placeholder_bits) { + return ty.super_visit_with(self, outer_binder); + } else { + // Fast path: don't visit inner types (e.g. generic arguments) when `flags` indicate + // that there are no placeholders. + } + + std::ops::ControlFlow::Continue(()) + } + + fn visit_const( + &mut self, + constant: &chalk_ir::Const<Interner>, + _outer_binder: DebruijnIndex, + ) -> std::ops::ControlFlow<Self::BreakTy> { + if let chalk_ir::ConstValue::Placeholder(idx) = constant.data(Interner).value { + self.collect(idx); + } + std::ops::ControlFlow::Continue(()) + } +} + +/// Returns unique placeholders for types and consts contained in `value`. +pub fn collect_placeholders<T>(value: &T, db: &dyn HirDatabase) -> Vec<TypeOrConstParamId> +where + T: ?Sized + TypeVisitable<Interner>, +{ + let mut collector = PlaceholderCollector { db, placeholders: FxHashSet::default() }; + value.visit_with(&mut collector, DebruijnIndex::INNERMOST); + collector.placeholders.into_iter().collect() +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs index 592410008..299646737 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -23,24 +23,24 @@ use hir_def::{ generics::{ TypeOrConstParamData, TypeParamProvenance, WherePredicate, WherePredicateTypeTarget, }, - intern::Interned, - lang_item::lang_attr, + lang_item::{lang_attr, LangItem}, path::{GenericArg, ModPath, Path, PathKind, PathSegment, PathSegments}, resolver::{HasResolver, Resolver, TypeNs}, type_ref::{ ConstScalarOrPath, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, TypeRef, }, AdtId, AssocItemId, ConstId, ConstParamId, EnumId, EnumVariantId, FunctionId, GenericDefId, - HasModule, ImplId, ItemContainerId, LocalFieldId, Lookup, StaticId, StructId, TraitId, - TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, VariantId, + HasModule, ImplId, ItemContainerId, LocalFieldId, Lookup, ModuleDefId, StaticId, StructId, + TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, VariantId, }; use hir_expand::{name::Name, ExpandResult}; +use intern::Interned; use itertools::Either; -use la_arena::ArenaMap; +use la_arena::{Arena, ArenaMap}; use rustc_hash::FxHashSet; use smallvec::SmallVec; use stdx::{impl_from, never}; -use syntax::{ast, SmolStr}; +use syntax::ast; use crate::{ all_super_traits, @@ -58,6 +58,51 @@ use crate::{ }; #[derive(Debug)] +enum ImplTraitLoweringState { + /// When turning `impl Trait` into opaque types, we have to collect the + /// bounds at the same time to get the IDs correct (without becoming too + /// complicated). I don't like using interior mutability (as for the + /// counter), but I've tried and failed to make the lifetimes work for + /// passing around a `&mut TyLoweringContext`. The core problem is that + /// we're grouping the mutable data (the counter and this field) together + /// with the immutable context (the references to the DB and resolver). + /// Splitting this up would be a possible fix. + Opaque(RefCell<Arena<ReturnTypeImplTrait>>), + Param(Cell<u16>), + Variable(Cell<u16>), + Disallowed, +} +impl ImplTraitLoweringState { + fn new(impl_trait_mode: ImplTraitLoweringMode) -> ImplTraitLoweringState { + match impl_trait_mode { + ImplTraitLoweringMode::Opaque => Self::Opaque(RefCell::new(Arena::new())), + ImplTraitLoweringMode::Param => Self::Param(Cell::new(0)), + ImplTraitLoweringMode::Variable => Self::Variable(Cell::new(0)), + ImplTraitLoweringMode::Disallowed => Self::Disallowed, + } + } + + fn take(&self) -> Self { + match self { + Self::Opaque(x) => Self::Opaque(RefCell::new(x.take())), + Self::Param(x) => Self::Param(Cell::new(x.get())), + Self::Variable(x) => Self::Variable(Cell::new(x.get())), + Self::Disallowed => Self::Disallowed, + } + } + + fn swap(&self, impl_trait_mode: &Self) { + match (self, impl_trait_mode) { + (Self::Opaque(x), Self::Opaque(y)) => x.swap(y), + (Self::Param(x), Self::Param(y)) => x.swap(y), + (Self::Variable(x), Self::Variable(y)) => x.swap(y), + (Self::Disallowed, Self::Disallowed) => (), + _ => panic!("mismatched lowering mode"), + } + } +} + +#[derive(Debug)] pub struct TyLoweringContext<'a> { pub db: &'a dyn HirDatabase, pub resolver: &'a Resolver, @@ -67,17 +112,7 @@ pub struct TyLoweringContext<'a> { /// should be converted to variables. I think in practice, this isn't /// possible currently, so this should be fine for now. pub type_param_mode: ParamLoweringMode, - pub impl_trait_mode: ImplTraitLoweringMode, - impl_trait_counter: Cell<u16>, - /// When turning `impl Trait` into opaque types, we have to collect the - /// bounds at the same time to get the IDs correct (without becoming too - /// complicated). I don't like using interior mutability (as for the - /// counter), but I've tried and failed to make the lifetimes work for - /// passing around a `&mut TyLoweringContext`. The core problem is that - /// we're grouping the mutable data (the counter and this field) together - /// with the immutable context (the references to the DB and resolver). - /// Splitting this up would be a possible fix. - opaque_type_data: RefCell<Vec<ReturnTypeImplTrait>>, + impl_trait_mode: ImplTraitLoweringState, expander: RefCell<Option<Expander>>, /// Tracks types with explicit `?Sized` bounds. pub(crate) unsized_types: RefCell<FxHashSet<Ty>>, @@ -85,19 +120,15 @@ pub struct TyLoweringContext<'a> { impl<'a> TyLoweringContext<'a> { pub fn new(db: &'a dyn HirDatabase, resolver: &'a Resolver) -> Self { - let impl_trait_counter = Cell::new(0); - let impl_trait_mode = ImplTraitLoweringMode::Disallowed; + let impl_trait_mode = ImplTraitLoweringState::Disallowed; let type_param_mode = ParamLoweringMode::Placeholder; let in_binders = DebruijnIndex::INNERMOST; - let opaque_type_data = RefCell::new(Vec::new()); Self { db, resolver, in_binders, impl_trait_mode, - impl_trait_counter, type_param_mode, - opaque_type_data, expander: RefCell::new(None), unsized_types: RefCell::default(), } @@ -108,20 +139,18 @@ impl<'a> TyLoweringContext<'a> { debruijn: DebruijnIndex, f: impl FnOnce(&TyLoweringContext<'_>) -> T, ) -> T { - let opaque_ty_data_vec = self.opaque_type_data.take(); + let impl_trait_mode = self.impl_trait_mode.take(); let expander = self.expander.take(); let unsized_types = self.unsized_types.take(); let new_ctx = Self { in_binders: debruijn, - impl_trait_counter: Cell::new(self.impl_trait_counter.get()), - opaque_type_data: RefCell::new(opaque_ty_data_vec), + impl_trait_mode, expander: RefCell::new(expander), unsized_types: RefCell::new(unsized_types), ..*self }; let result = f(&new_ctx); - self.impl_trait_counter.set(new_ctx.impl_trait_counter.get()); - self.opaque_type_data.replace(new_ctx.opaque_type_data.into_inner()); + self.impl_trait_mode.swap(&new_ctx.impl_trait_mode); self.expander.replace(new_ctx.expander.into_inner()); self.unsized_types.replace(new_ctx.unsized_types.into_inner()); result @@ -136,7 +165,7 @@ impl<'a> TyLoweringContext<'a> { } pub fn with_impl_trait_mode(self, impl_trait_mode: ImplTraitLoweringMode) -> Self { - Self { impl_trait_mode, ..self } + Self { impl_trait_mode: ImplTraitLoweringState::new(impl_trait_mode), ..self } } pub fn with_type_param_mode(self, type_param_mode: ParamLoweringMode) -> Self { @@ -244,20 +273,17 @@ impl<'a> TyLoweringContext<'a> { } TypeRef::DynTrait(bounds) => self.lower_dyn_trait(bounds), TypeRef::ImplTrait(bounds) => { - match self.impl_trait_mode { - ImplTraitLoweringMode::Opaque => { - let idx = self.impl_trait_counter.get(); - self.impl_trait_counter.set(idx + 1); + match &self.impl_trait_mode { + ImplTraitLoweringState::Opaque(opaque_type_data) => { let func = match self.resolver.generic_def() { Some(GenericDefId::FunctionId(f)) => f, _ => panic!("opaque impl trait lowering in non-function"), }; - assert!(idx as usize == self.opaque_type_data.borrow().len()); // this dance is to make sure the data is in the right // place even if we encounter more opaque types while // lowering the bounds - self.opaque_type_data.borrow_mut().push(ReturnTypeImplTrait { + let idx = opaque_type_data.borrow_mut().alloc(ReturnTypeImplTrait { bounds: crate::make_single_type_binders(Vec::new()), }); // We don't want to lower the bounds inside the binders @@ -273,7 +299,7 @@ impl<'a> TyLoweringContext<'a> { .with_debruijn(DebruijnIndex::INNERMOST, |ctx| { ctx.lower_impl_trait(bounds, func) }); - self.opaque_type_data.borrow_mut()[idx as usize] = actual_opaque_type_data; + opaque_type_data.borrow_mut()[idx] = actual_opaque_type_data; let impl_trait_id = ImplTraitId::ReturnTypeImplTrait(func, idx); let opaque_ty_id = self.db.intern_impl_trait_id(impl_trait_id).into(); @@ -281,10 +307,10 @@ impl<'a> TyLoweringContext<'a> { let parameters = generics.bound_vars_subst(self.db, self.in_binders); TyKind::OpaqueType(opaque_ty_id, parameters).intern(Interner) } - ImplTraitLoweringMode::Param => { - let idx = self.impl_trait_counter.get(); + ImplTraitLoweringState::Param(counter) => { + let idx = counter.get(); // FIXME we're probably doing something wrong here - self.impl_trait_counter.set(idx + count_impl_traits(type_ref) as u16); + counter.set(idx + count_impl_traits(type_ref) as u16); if let Some(def) = self.resolver.generic_def() { let generics = generics(self.db.upcast(), def); let param = generics @@ -305,10 +331,10 @@ impl<'a> TyLoweringContext<'a> { TyKind::Error.intern(Interner) } } - ImplTraitLoweringMode::Variable => { - let idx = self.impl_trait_counter.get(); + ImplTraitLoweringState::Variable(counter) => { + let idx = counter.get(); // FIXME we're probably doing something wrong here - self.impl_trait_counter.set(idx + count_impl_traits(type_ref) as u16); + counter.set(idx + count_impl_traits(type_ref) as u16); let ( _parent_params, self_params, @@ -327,7 +353,7 @@ impl<'a> TyLoweringContext<'a> { )) .intern(Interner) } - ImplTraitLoweringMode::Disallowed => { + ImplTraitLoweringState::Disallowed => { // FIXME: report error TyKind::Error.intern(Interner) } @@ -954,7 +980,7 @@ impl<'a> TyLoweringContext<'a> { TypeBound::Path(path, TraitBoundModifier::Maybe) => { let sized_trait = self .db - .lang_item(self.resolver.krate(), SmolStr::new_inline("sized")) + .lang_item(self.resolver.krate(), LangItem::Sized) .and_then(|lang_item| lang_item.as_trait()); // Don't lower associated type bindings as the only possible relaxed trait bound // `?Sized` has no of them. @@ -999,7 +1025,7 @@ impl<'a> TyLoweringContext<'a> { last_segment .into_iter() .filter_map(|segment| segment.args_and_bindings) - .flat_map(|args_and_bindings| &args_and_bindings.bindings) + .flat_map(|args_and_bindings| args_and_bindings.bindings.iter()) .flat_map(move |binding| { let found = associated_type_by_name_including_super_traits( self.db, @@ -1042,7 +1068,7 @@ impl<'a> TyLoweringContext<'a> { AliasEq { alias: AliasTy::Projection(projection_ty.clone()), ty }; preds.push(crate::wrap_empty_binders(WhereClause::AliasEq(alias_eq))); } - for bound in &binding.bounds { + for bound in binding.bounds.iter() { preds.extend(self.lower_type_bound( bound, TyKind::Alias(AliasTy::Projection(projection_ty.clone())).intern(Interner), @@ -1150,7 +1176,7 @@ impl<'a> TyLoweringContext<'a> { let krate = func.lookup(ctx.db.upcast()).module(ctx.db.upcast()).krate(); let sized_trait = ctx .db - .lang_item(krate, SmolStr::new_inline("sized")) + .lang_item(krate, LangItem::Sized) .and_then(|lang_item| lang_item.as_trait().map(to_chalk_trait_id)); let sized_clause = sized_trait.map(|trait_id| { let clause = WhereClause::Implemented(TraitRef { @@ -1209,7 +1235,7 @@ fn named_associated_type_shorthand_candidates<R>( mut cb: impl FnMut(&Name, &TraitRef, TypeAliasId) -> Option<R>, ) -> Option<R> { let mut search = |t| { - for t in all_super_trait_refs(db, t) { + all_super_trait_refs(db, t, |t| { let data = db.trait_data(t.hir_trait_id()); for (name, assoc_id) in &data.items { @@ -1219,8 +1245,8 @@ fn named_associated_type_shorthand_candidates<R>( } } } - } - None + None + }) }; match res { @@ -1489,7 +1515,7 @@ fn implicitly_sized_clauses<'a>( let is_trait_def = matches!(def, GenericDefId::TraitId(..)); let generic_args = &substitution.as_slice(Interner)[is_trait_def as usize..]; let sized_trait = db - .lang_item(resolver.krate(), SmolStr::new_inline("sized")) + .lang_item(resolver.krate(), LangItem::Sized) .and_then(|lang_item| lang_item.as_trait().map(to_chalk_trait_id)); sized_trait.into_iter().flat_map(move |sized_trait| { @@ -1704,6 +1730,15 @@ pub enum CallableDefId { EnumVariantId(EnumVariantId), } impl_from!(FunctionId, StructId, EnumVariantId for CallableDefId); +impl From<CallableDefId> for ModuleDefId { + fn from(def: CallableDefId) -> ModuleDefId { + match def { + CallableDefId::FunctionId(f) => ModuleDefId::FunctionId(f), + CallableDefId::StructId(s) => ModuleDefId::AdtId(AdtId::StructId(s)), + CallableDefId::EnumVariantId(e) => ModuleDefId::EnumVariantId(e), + } + } +} impl CallableDefId { pub fn krate(self, db: &dyn HirDatabase) -> CrateId { @@ -1854,8 +1889,12 @@ pub(crate) fn return_type_impl_traits( .with_type_param_mode(ParamLoweringMode::Variable); let _ret = ctx_ret.lower_ty(&data.ret_type); let generics = generics(db.upcast(), def.into()); - let return_type_impl_traits = - ReturnTypeImplTraits { impl_traits: ctx_ret.opaque_type_data.into_inner() }; + let return_type_impl_traits = ReturnTypeImplTraits { + impl_traits: match ctx_ret.impl_trait_mode { + ImplTraitLoweringState::Opaque(x) => x.into_inner(), + _ => unreachable!(), + }, + }; if return_type_impl_traits.impl_traits.is_empty() { None } else { @@ -1931,7 +1970,7 @@ pub(crate) fn const_or_path_to_chalk( debruijn: DebruijnIndex, ) -> Const { match value { - ConstScalarOrPath::Scalar(s) => intern_const_scalar(s.clone(), expected_ty), + ConstScalarOrPath::Scalar(s) => intern_const_scalar(*s, expected_ty), ConstScalarOrPath::Path(n) => { let path = ModPath::from_segments(PathKind::Plain, Some(n.clone())); path_to_const(db, resolver, &path, mode, args, debruijn) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs index 2328dceb8..8c7714b9a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs @@ -5,10 +5,11 @@ use std::{ops::ControlFlow, sync::Arc}; use base_db::{CrateId, Edition}; -use chalk_ir::{cast::Cast, Mutability, UniverseIndex}; +use chalk_ir::{cast::Cast, Mutability, TyKind, UniverseIndex}; use hir_def::{ - data::ImplData, item_scope::ItemScope, nameres::DefMap, AssocItemId, BlockId, ConstId, - FunctionId, HasModule, ImplId, ItemContainerId, Lookup, ModuleDefId, ModuleId, TraitId, + data::ImplData, item_scope::ItemScope, lang_item::LangItem, nameres::DefMap, AssocItemId, + BlockId, ConstId, FunctionId, HasModule, ImplId, ItemContainerId, Lookup, ModuleDefId, + ModuleId, TraitId, }; use hir_expand::name::Name; use rustc_hash::{FxHashMap, FxHashSet}; @@ -24,7 +25,7 @@ use crate::{ static_lifetime, to_chalk_trait_id, utils::all_super_traits, AdtId, Canonical, CanonicalVarKinds, DebruijnIndex, ForeignDefId, InEnvironment, Interner, - Scalar, Substitution, TraitEnvironment, TraitRef, TraitRefExt, Ty, TyBuilder, TyExt, TyKind, + Scalar, Substitution, TraitEnvironment, TraitRef, TraitRefExt, Ty, TyBuilder, TyExt, }; /// This is used as a key for indexing impls. @@ -437,49 +438,49 @@ pub fn def_crates( } } -pub fn lang_names_for_bin_op(op: syntax::ast::BinaryOp) -> Option<(Name, Name)> { +pub fn lang_items_for_bin_op(op: syntax::ast::BinaryOp) -> Option<(Name, LangItem)> { use hir_expand::name; use syntax::ast::{ArithOp, BinaryOp, CmpOp, Ordering}; Some(match op { BinaryOp::LogicOp(_) => return None, BinaryOp::ArithOp(aop) => match aop { - ArithOp::Add => (name!(add), name!(add)), - ArithOp::Mul => (name!(mul), name!(mul)), - ArithOp::Sub => (name!(sub), name!(sub)), - ArithOp::Div => (name!(div), name!(div)), - ArithOp::Rem => (name!(rem), name!(rem)), - ArithOp::Shl => (name!(shl), name!(shl)), - ArithOp::Shr => (name!(shr), name!(shr)), - ArithOp::BitXor => (name!(bitxor), name!(bitxor)), - ArithOp::BitOr => (name!(bitor), name!(bitor)), - ArithOp::BitAnd => (name!(bitand), name!(bitand)), + ArithOp::Add => (name![add], LangItem::Add), + ArithOp::Mul => (name![mul], LangItem::Mul), + ArithOp::Sub => (name![sub], LangItem::Sub), + ArithOp::Div => (name![div], LangItem::Div), + ArithOp::Rem => (name![rem], LangItem::Rem), + ArithOp::Shl => (name![shl], LangItem::Shl), + ArithOp::Shr => (name![shr], LangItem::Shr), + ArithOp::BitXor => (name![bitxor], LangItem::BitXor), + ArithOp::BitOr => (name![bitor], LangItem::BitOr), + ArithOp::BitAnd => (name![bitand], LangItem::BitAnd), }, BinaryOp::Assignment { op: Some(aop) } => match aop { - ArithOp::Add => (name!(add_assign), name!(add_assign)), - ArithOp::Mul => (name!(mul_assign), name!(mul_assign)), - ArithOp::Sub => (name!(sub_assign), name!(sub_assign)), - ArithOp::Div => (name!(div_assign), name!(div_assign)), - ArithOp::Rem => (name!(rem_assign), name!(rem_assign)), - ArithOp::Shl => (name!(shl_assign), name!(shl_assign)), - ArithOp::Shr => (name!(shr_assign), name!(shr_assign)), - ArithOp::BitXor => (name!(bitxor_assign), name!(bitxor_assign)), - ArithOp::BitOr => (name!(bitor_assign), name!(bitor_assign)), - ArithOp::BitAnd => (name!(bitand_assign), name!(bitand_assign)), + ArithOp::Add => (name![add_assign], LangItem::AddAssign), + ArithOp::Mul => (name![mul_assign], LangItem::MulAssign), + ArithOp::Sub => (name![sub_assign], LangItem::SubAssign), + ArithOp::Div => (name![div_assign], LangItem::DivAssign), + ArithOp::Rem => (name![rem_assign], LangItem::RemAssign), + ArithOp::Shl => (name![shl_assign], LangItem::ShlAssign), + ArithOp::Shr => (name![shr_assign], LangItem::ShrAssign), + ArithOp::BitXor => (name![bitxor_assign], LangItem::BitXorAssign), + ArithOp::BitOr => (name![bitor_assign], LangItem::BitOrAssign), + ArithOp::BitAnd => (name![bitand_assign], LangItem::BitAndAssign), }, BinaryOp::CmpOp(cop) => match cop { - CmpOp::Eq { negated: false } => (name!(eq), name!(eq)), - CmpOp::Eq { negated: true } => (name!(ne), name!(eq)), + CmpOp::Eq { negated: false } => (name![eq], LangItem::PartialEq), + CmpOp::Eq { negated: true } => (name![ne], LangItem::PartialEq), CmpOp::Ord { ordering: Ordering::Less, strict: false } => { - (name!(le), name!(partial_ord)) + (name![le], LangItem::PartialOrd) } CmpOp::Ord { ordering: Ordering::Less, strict: true } => { - (name!(lt), name!(partial_ord)) + (name![lt], LangItem::PartialOrd) } CmpOp::Ord { ordering: Ordering::Greater, strict: false } => { - (name!(ge), name!(partial_ord)) + (name![ge], LangItem::PartialOrd) } CmpOp::Ord { ordering: Ordering::Greater, strict: true } => { - (name!(gt), name!(partial_ord)) + (name![gt], LangItem::PartialOrd) } }, BinaryOp::Assignment { op: None } => return None, @@ -587,25 +588,31 @@ impl ReceiverAdjustments { } } } + if let Some(m) = self.autoref { + ty = TyKind::Ref(m, static_lifetime(), ty).intern(Interner); + adjust + .push(Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(m)), target: ty.clone() }); + } if self.unsize_array { - ty = match ty.kind(Interner) { - TyKind::Array(inner, _) => TyKind::Slice(inner.clone()).intern(Interner), - _ => { - never!("unsize_array with non-array {:?}", ty); - ty + ty = 'x: { + if let TyKind::Ref(m, l, inner) = ty.kind(Interner) { + if let TyKind::Array(inner, _) = inner.kind(Interner) { + break 'x TyKind::Ref( + m.clone(), + l.clone(), + TyKind::Slice(inner.clone()).intern(Interner), + ) + .intern(Interner); + } } + never!("unsize_array with non-reference-to-array {:?}", ty); + ty }; - // FIXME this is kind of wrong since the unsize needs to happen to a pointer/reference adjust.push(Adjustment { kind: Adjust::Pointer(PointerCast::Unsize), target: ty.clone(), }); } - if let Some(m) = self.autoref { - ty = TyKind::Ref(m, static_lifetime(), ty).intern(Interner); - adjust - .push(Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(m)), target: ty.clone() }); - } (ty, adjust) } @@ -712,17 +719,17 @@ fn lookup_impl_assoc_item_for_trait_ref( let table = InferenceTable::new(db, env); let impl_data = find_matching_impl(impls, table, trait_ref)?; - impl_data.items.iter().find_map(|it| match it { + impl_data.items.iter().find_map(|&it| match it { AssocItemId::FunctionId(f) => { - (db.function_data(*f).name == *name).then_some(AssocItemId::FunctionId(*f)) + (db.function_data(f).name == *name).then_some(AssocItemId::FunctionId(f)) } AssocItemId::ConstId(c) => db - .const_data(*c) + .const_data(c) .name .as_ref() - .map(|n| *n == *name) - .and_then(|result| if result { Some(AssocItemId::ConstId(*c)) } else { None }), - _ => None, + .map(|n| n == name) + .and_then(|result| if result { Some(AssocItemId::ConstId(c)) } else { None }), + AssocItemId::TypeAliasId(_) => None, }) } @@ -1094,13 +1101,13 @@ fn iterate_inherent_methods( None => return ControlFlow::Continue(()), }; - let (module, block) = match visible_from_module { + let (module, mut block) = match visible_from_module { VisibleFromModule::Filter(module) => (Some(module), module.containing_block()), VisibleFromModule::IncludeBlock(block) => (None, Some(block)), VisibleFromModule::None => (None, None), }; - if let Some(block_id) = block { + while let Some(block_id) = block { if let Some(impls) = db.inherent_impls_in_block(block_id) { impls_for_self_ty( &impls, @@ -1113,6 +1120,11 @@ fn iterate_inherent_methods( callback, )?; } + + block = db + .block_def_map(block_id) + .and_then(|map| map.parent()) + .and_then(|module| module.containing_block()); } for krate in def_crates { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs index 6c7a53299..41c53701d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs @@ -813,7 +813,7 @@ fn test() { fn method_resolution_trait_from_prelude() { check_types( r#" -//- /main.rs crate:main deps:core +//- /main.rs edition:2018 crate:main deps:core struct S; impl Clone for S {} @@ -986,14 +986,13 @@ fn main() { } #[test] -fn method_resolution_encountering_fn_type() { +fn explicit_fn_once_call_fn_item() { check_types( r#" -//- /main.rs +//- minicore: fn fn foo() {} -trait FnOnce { fn call(self); } -fn test() { foo.call(); } - //^^^^^^^^^^ {unknown} +fn test() { foo.call_once(); } + //^^^^^^^^^^^^^^^ () "#, ); } @@ -1527,7 +1526,7 @@ fn f(x: U2) { fn skip_array_during_method_dispatch() { check_types( r#" -//- /main2018.rs crate:main2018 deps:core +//- /main2018.rs crate:main2018 deps:core edition:2018 use core::IntoIterator; fn f() { @@ -1725,14 +1724,13 @@ fn test() { #[test] fn receiver_adjustment_unsize_array() { - // FIXME not quite correct check( r#" //- minicore: slice fn test() { let a = [1, 2, 3]; a.len(); -} //^ adjustments: Pointer(Unsize), Borrow(Ref(Not)) +} //^ adjustments: Borrow(Ref(Not)), Pointer(Unsize) "#, ); } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs index 146145523..2e5787b70 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs @@ -3200,3 +3200,86 @@ fn func() { "#, ); } + +// FIXME +#[test] +fn castable_to() { + check_infer( + r#" +//- minicore: sized +#[lang = "owned_box"] +pub struct Box<T: ?Sized> { + inner: *mut T, +} +impl<T> Box<T> { + fn new(t: T) -> Self { loop {} } +} + +fn func() { + let x = Box::new([]) as Box<[i32; 0]>; +} +"#, + expect![[r#" + 99..100 't': T + 113..124 '{ loop {} }': Box<T> + 115..122 'loop {}': ! + 120..122 '{}': () + 138..184 '{ ...0]>; }': () + 148..149 'x': Box<[i32; 0]> + 152..160 'Box::new': fn new<[{unknown}; 0]>([{unknown}; 0]) -> Box<[{unknown}; 0]> + 152..164 'Box::new([])': Box<[{unknown}; 0]> + 152..181 'Box::n...2; 0]>': Box<[i32; 0]> + 161..163 '[]': [{unknown}; 0] + "#]], + ); +} + +#[test] +fn castable_to1() { + check_infer( + r#" +struct Ark<T>(T); +impl<T> Ark<T> { + fn foo(&self) -> *const T { + &self.0 + } +} +fn f<T>(t: Ark<T>) { + Ark::foo(&t) as *const (); +} +"#, + expect![[r#" + 47..51 'self': &Ark<T> + 65..88 '{ ... }': *const T + 75..82 '&self.0': &T + 76..80 'self': &Ark<T> + 76..82 'self.0': T + 99..100 't': Ark<T> + 110..144 '{ ... (); }': () + 116..124 'Ark::foo': fn foo<T>(&Ark<T>) -> *const T + 116..128 'Ark::foo(&t)': *const T + 116..141 'Ark::f...nst ()': *const () + 125..127 '&t': &Ark<T> + 126..127 't': Ark<T> + "#]], + ); +} + +// FIXME +#[test] +fn castable_to2() { + check_infer( + r#" +fn func() { + let x = &0u32 as *const _; +} +"#, + expect![[r#" + 10..44 '{ ...t _; }': () + 20..21 'x': *const {unknown} + 24..29 '&0u32': &u32 + 24..41 '&0u32 ...onst _': *const {unknown} + 25..29 '0u32': u32 + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs index d01fe0632..015085bde 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs @@ -163,98 +163,22 @@ fn test() { } #[test] -fn infer_try() { +fn infer_try_trait() { check_types( r#" -//- /main.rs crate:main deps:core +//- minicore: try, result fn test() { let r: Result<i32, u64> = Result::Ok(1); let v = r?; v; } //^ i32 -//- /core.rs crate:core -pub mod ops { - pub trait Try { - type Ok; - type Error; - } -} - -pub mod result { - pub enum Result<O, E> { - Ok(O), - Err(E) - } - - impl<O, E> crate::ops::Try for Result<O, E> { - type Ok = O; - type Error = E; - } -} - -pub mod prelude { - pub mod rust_2018 { - pub use crate::{result::*, ops::*}; - } -} -"#, - ); -} - -#[test] -fn infer_try_trait_v2() { - check_types( - r#" -//- /main.rs crate:main deps:core -fn test() { - let r: Result<i32, u64> = Result::Ok(1); - let v = r?; - v; -} //^ i32 - -//- /core.rs crate:core -mod ops { - mod try_trait { - pub trait Try: FromResidual { - type Output; - type Residual; - } - pub trait FromResidual<R = <Self as Try>::Residual> {} - } - - pub use self::try_trait::FromResidual; - pub use self::try_trait::Try; +impl<O, E> core::ops::Try for Result<O, E> { + type Output = O; + type Error = Result<core::convert::Infallible, E>; } -mod convert { - pub trait From<T> {} - impl<T> From<T> for T {} -} - -pub mod result { - use crate::convert::From; - use crate::ops::{Try, FromResidual}; - - pub enum Infallible {} - pub enum Result<O, E> { - Ok(O), - Err(E) - } - - impl<O, E> Try for Result<O, E> { - type Output = O; - type Error = Result<Infallible, E>; - } - - impl<T, E, F: From<E>> FromResidual<Result<Infallible, E>> for Result<T, F> {} -} - -pub mod prelude { - pub mod rust_2018 { - pub use crate::result::*; - } -} +impl<T, E, F: From<E>> core::ops::FromResidual<Result<core::convert::Infallible, E>> for Result<T, F> {} "#, ); } @@ -263,7 +187,8 @@ pub mod prelude { fn infer_for_loop() { check_types( r#" -//- /main.rs crate:main deps:core,alloc +//- minicore: iterator +//- /main.rs crate:main deps:alloc #![no_std] use alloc::collections::Vec; @@ -275,23 +200,7 @@ fn test() { } //^ &str } -//- /core.rs crate:core -pub mod iter { - pub trait IntoIterator { - type Item; - type IntoIter: Iterator<Item = Self::Item>; - } - pub trait Iterator { - type Item; - } -} -pub mod prelude { - pub mod rust_2018 { - pub use crate::iter::*; - } -} - -//- /alloc.rs crate:alloc deps:core +//- /alloc.rs crate:alloc #![no_std] pub mod collections { pub struct Vec<T> {} @@ -1848,25 +1757,19 @@ fn test() { fn fn_trait() { check_infer_with_mismatches( r#" -trait FnOnce<Args> { - type Output; - - fn call_once(self, args: Args) -> <Self as FnOnce<Args>>::Output; -} +//- minicore: fn fn test<F: FnOnce(u32, u64) -> u128>(f: F) { f.call_once((1, 2)); }"#, expect![[r#" - 56..60 'self': Self - 62..66 'args': Args - 149..150 'f': F - 155..183 '{ ...2)); }': () - 161..162 'f': F - 161..180 'f.call...1, 2))': u128 - 173..179 '(1, 2)': (u32, u64) - 174..175 '1': u32 - 177..178 '2': u64 + 38..39 'f': F + 44..72 '{ ...2)); }': () + 50..51 'f': F + 50..69 'f.call...1, 2))': u128 + 62..68 '(1, 2)': (u32, u64) + 63..64 '1': u32 + 66..67 '2': u64 "#]], ); } @@ -1875,12 +1778,7 @@ fn test<F: FnOnce(u32, u64) -> u128>(f: F) { fn fn_ptr_and_item() { check_infer_with_mismatches( r#" -#[lang="fn_once"] -trait FnOnce<Args> { - type Output; - - fn call_once(self, args: Args) -> Self::Output; -} +//- minicore: fn trait Foo<T> { fn foo(&self) -> T; @@ -1906,27 +1804,25 @@ fn test() { opt.map(f); }"#, expect![[r#" - 74..78 'self': Self - 80..84 'args': Args - 139..143 'self': &Self - 243..247 'self': &Bar<F> - 260..271 '{ loop {} }': (A1, R) - 262..269 'loop {}': ! - 267..269 '{}': () - 355..359 'self': Opt<T> - 361..362 'f': F - 377..388 '{ loop {} }': Opt<U> - 379..386 'loop {}': ! - 384..386 '{}': () - 402..518 '{ ...(f); }': () - 412..415 'bar': Bar<fn(u8) -> u32> - 441..444 'bar': Bar<fn(u8) -> u32> - 441..450 'bar.foo()': (u8, u32) - 461..464 'opt': Opt<u8> - 483..484 'f': fn(u8) -> u32 - 505..508 'opt': Opt<u8> - 505..515 'opt.map(f)': Opt<u32> - 513..514 'f': fn(u8) -> u32 + 28..32 'self': &Self + 132..136 'self': &Bar<F> + 149..160 '{ loop {} }': (A1, R) + 151..158 'loop {}': ! + 156..158 '{}': () + 244..248 'self': Opt<T> + 250..251 'f': F + 266..277 '{ loop {} }': Opt<U> + 268..275 'loop {}': ! + 273..275 '{}': () + 291..407 '{ ...(f); }': () + 301..304 'bar': Bar<fn(u8) -> u32> + 330..333 'bar': Bar<fn(u8) -> u32> + 330..339 'bar.foo()': (u8, u32) + 350..353 'opt': Opt<u8> + 372..373 'f': fn(u8) -> u32 + 394..397 'opt': Opt<u8> + 394..404 'opt.map(f)': Opt<u32> + 402..403 'f': fn(u8) -> u32 "#]], ); } @@ -2399,10 +2295,8 @@ fn unselected_projection_in_trait_env_no_cycle() { // this is not a cycle check_types( r#" -//- /main.rs -trait Index { - type Output; -} +//- minicore: index +use core::ops::Index; type Key<S: UnificationStoreBase> = <S as UnificationStoreBase>::Key; @@ -2999,40 +2893,17 @@ fn test() { fn integer_range_iterate() { check_types( r#" -//- /main.rs crate:main deps:core +//- minicore: range, iterator +//- /main.rs crate:main fn test() { for x in 0..100 { x; } } //^ i32 -//- /core.rs crate:core -pub mod ops { - pub struct Range<Idx> { - pub start: Idx, - pub end: Idx, - } -} - -pub mod iter { - pub trait Iterator { - type Item; - } - - pub trait IntoIterator { - type Item; - type IntoIter: Iterator<Item = Self::Item>; - } - - impl<T> IntoIterator for T where T: Iterator { - type Item = <T as Iterator>::Item; - type IntoIter = Self; - } -} - trait Step {} impl Step for i32 {} impl Step for i64 {} -impl<A: Step> iter::Iterator for ops::Range<A> { +impl<A: Step> core::iter::Iterator for core::ops::Range<A> { type Item = A; } "#, @@ -3507,14 +3378,9 @@ trait Request { fn bin_op_adt_with_rhs_primitive() { check_infer_with_mismatches( r#" -#[lang = "add"] -pub trait Add<Rhs = Self> { - type Output; - fn add(self, rhs: Rhs) -> Self::Output; -} - +//- minicore: add struct Wrapper(u32); -impl Add<u32> for Wrapper { +impl core::ops::Add<u32> for Wrapper { type Output = Self; fn add(self, rhs: u32) -> Wrapper { Wrapper(rhs) @@ -3527,30 +3393,107 @@ fn main(){ }"#, expect![[r#" - 72..76 'self': Self - 78..81 'rhs': Rhs - 192..196 'self': Wrapper - 198..201 'rhs': u32 - 219..247 '{ ... }': Wrapper - 229..236 'Wrapper': Wrapper(u32) -> Wrapper - 229..241 'Wrapper(rhs)': Wrapper - 237..240 'rhs': u32 - 259..345 '{ ...um; }': () - 269..276 'wrapped': Wrapper - 279..286 'Wrapper': Wrapper(u32) -> Wrapper - 279..290 'Wrapper(10)': Wrapper - 287..289 '10': u32 - 300..303 'num': u32 - 311..312 '2': u32 - 322..325 'res': Wrapper - 328..335 'wrapped': Wrapper - 328..341 'wrapped + num': Wrapper - 338..341 'num': u32 + 95..99 'self': Wrapper + 101..104 'rhs': u32 + 122..150 '{ ... }': Wrapper + 132..139 'Wrapper': Wrapper(u32) -> Wrapper + 132..144 'Wrapper(rhs)': Wrapper + 140..143 'rhs': u32 + 162..248 '{ ...um; }': () + 172..179 'wrapped': Wrapper + 182..189 'Wrapper': Wrapper(u32) -> Wrapper + 182..193 'Wrapper(10)': Wrapper + 190..192 '10': u32 + 203..206 'num': u32 + 214..215 '2': u32 + 225..228 'res': Wrapper + 231..238 'wrapped': Wrapper + 231..244 'wrapped + num': Wrapper + 241..244 'num': u32 "#]], ) } #[test] +fn builtin_binop_expectation_works_on_single_reference() { + check_types( + r#" +//- minicore: add +use core::ops::Add; +impl Add<i32> for i32 { type Output = i32 } +impl Add<&i32> for i32 { type Output = i32 } +impl Add<u32> for u32 { type Output = u32 } +impl Add<&u32> for u32 { type Output = u32 } + +struct V<T>; +impl<T> V<T> { + fn default() -> Self { loop {} } + fn get(&self, _: &T) -> &T { loop {} } +} + +fn take_u32(_: u32) {} +fn minimized() { + let v = V::default(); + let p = v.get(&0); + //^ &u32 + take_u32(42 + p); +} +"#, + ); +} + +#[test] +fn no_builtin_binop_expectation_for_general_ty_var() { + // FIXME: Ideally type mismatch should be reported on `take_u32(42 - p)`. + check_types( + r#" +//- minicore: add +use core::ops::Add; +impl Add<i32> for i32 { type Output = i32; } +impl Add<&i32> for i32 { type Output = i32; } +// This is needed to prevent chalk from giving unique solution to `i32: Add<&?0>` after applying +// fallback to integer type variable for `42`. +impl Add<&()> for i32 { type Output = (); } + +struct V<T>; +impl<T> V<T> { + fn default() -> Self { loop {} } + fn get(&self) -> &T { loop {} } +} + +fn take_u32(_: u32) {} +fn minimized() { + let v = V::default(); + let p = v.get(); + //^ &{unknown} + take_u32(42 + p); +} +"#, + ); +} + +#[test] +fn no_builtin_binop_expectation_for_non_builtin_types() { + check_no_mismatches( + r#" +//- minicore: default, eq +struct S; +impl Default for S { fn default() -> Self { S } } +impl Default for i32 { fn default() -> Self { 0 } } +impl PartialEq<S> for i32 { fn eq(&self, _: &S) -> bool { true } } +impl PartialEq<i32> for i32 { fn eq(&self, _: &S) -> bool { true } } + +fn take_s(_: S) {} +fn test() { + let s = Default::default(); + let _eq = 0 == s; + take_s(s); +} +"#, + ) +} + +#[test] fn array_length() { check_infer( r#" diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs index 778a6b820..3ab85c68f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs @@ -7,9 +7,11 @@ use chalk_recursive::Cache; use chalk_solve::{logging_db::LoggingRustIrDatabase, Solver}; use base_db::CrateId; -use hir_def::{lang_item::LangItemTarget, TraitId}; +use hir_def::{ + lang_item::{LangItem, LangItemTarget}, + TraitId, +}; use stdx::panic_context; -use syntax::SmolStr; use crate::{ db::HirDatabase, infer::unify::InferenceTable, AliasEq, AliasTy, Canonical, DomainGoal, Goal, @@ -177,18 +179,18 @@ pub enum FnTrait { } impl FnTrait { - const fn lang_item_name(self) -> &'static str { + const fn lang_item(self) -> LangItem { match self { - FnTrait::FnOnce => "fn_once", - FnTrait::FnMut => "fn_mut", - FnTrait::Fn => "fn", + FnTrait::FnOnce => LangItem::FnOnce, + FnTrait::FnMut => LangItem::FnMut, + FnTrait::Fn => LangItem::Fn, } } pub fn get_id(&self, db: &dyn HirDatabase, krate: CrateId) -> Option<TraitId> { - let target = db.lang_item(krate, SmolStr::new_inline(self.lang_item_name()))?; + let target = db.lang_item(krate, self.lang_item())?; match target { - LangItemTarget::TraitId(t) => Some(t), + LangItemTarget::Trait(t) => Some(t), _ => None, } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs b/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs index 9893566bd..70d2d5efa 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs @@ -11,39 +11,100 @@ use hir_def::{ GenericParams, TypeOrConstParamData, TypeParamProvenance, WherePredicate, WherePredicateTypeTarget, }, - intern::Interned, + lang_item::LangItem, resolver::{HasResolver, TypeNs}, type_ref::{TraitBoundModifier, TypeRef}, ConstParamId, FunctionId, GenericDefId, ItemContainerId, Lookup, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, }; use hir_expand::name::Name; +use intern::Interned; use itertools::Either; use rustc_hash::FxHashSet; use smallvec::{smallvec, SmallVec}; -use syntax::SmolStr; use crate::{ db::HirDatabase, ChalkTraitId, Interner, Substitution, TraitRef, TraitRefExt, WhereClause, }; -pub(crate) fn fn_traits(db: &dyn DefDatabase, krate: CrateId) -> impl Iterator<Item = TraitId> { - [ - db.lang_item(krate, SmolStr::new_inline("fn")), - db.lang_item(krate, SmolStr::new_inline("fn_mut")), - db.lang_item(krate, SmolStr::new_inline("fn_once")), - ] - .into_iter() - .flatten() - .flat_map(|it| it.as_trait()) +pub(crate) fn fn_traits( + db: &dyn DefDatabase, + krate: CrateId, +) -> impl Iterator<Item = TraitId> + '_ { + [LangItem::Fn, LangItem::FnMut, LangItem::FnOnce] + .into_iter() + .filter_map(move |lang| db.lang_item(krate, lang)) + .flat_map(|it| it.as_trait()) } -fn direct_super_traits(db: &dyn DefDatabase, trait_: TraitId) -> SmallVec<[TraitId; 4]> { +/// Returns an iterator over the whole super trait hierarchy (including the +/// trait itself). +pub fn all_super_traits(db: &dyn DefDatabase, trait_: TraitId) -> SmallVec<[TraitId; 4]> { + // we need to take care a bit here to avoid infinite loops in case of cycles + // (i.e. if we have `trait A: B; trait B: A;`) + + let mut result = smallvec![trait_]; + let mut i = 0; + while let Some(&t) = result.get(i) { + // yeah this is quadratic, but trait hierarchies should be flat + // enough that this doesn't matter + direct_super_traits(db, t, |tt| { + if !result.contains(&tt) { + result.push(tt); + } + }); + i += 1; + } + result +} + +/// Given a trait ref (`Self: Trait`), builds all the implied trait refs for +/// super traits. The original trait ref will be included. So the difference to +/// `all_super_traits` is that we keep track of type parameters; for example if +/// we have `Self: Trait<u32, i32>` and `Trait<T, U>: OtherTrait<U>` we'll get +/// `Self: OtherTrait<i32>`. +pub(super) fn all_super_trait_refs<T>( + db: &dyn HirDatabase, + trait_ref: TraitRef, + cb: impl FnMut(TraitRef) -> Option<T>, +) -> Option<T> { + let seen = iter::once(trait_ref.trait_id).collect(); + let mut stack = Vec::new(); + stack.push(trait_ref); + SuperTraits { db, seen, stack }.find_map(cb) +} + +struct SuperTraits<'a> { + db: &'a dyn HirDatabase, + stack: Vec<TraitRef>, + seen: FxHashSet<ChalkTraitId>, +} + +impl<'a> SuperTraits<'a> { + fn elaborate(&mut self, trait_ref: &TraitRef) { + direct_super_trait_refs(self.db, trait_ref, |trait_ref| { + if !self.seen.contains(&trait_ref.trait_id) { + self.stack.push(trait_ref); + } + }); + } +} + +impl<'a> Iterator for SuperTraits<'a> { + type Item = TraitRef; + + fn next(&mut self) -> Option<Self::Item> { + if let Some(next) = self.stack.pop() { + self.elaborate(&next); + Some(next) + } else { + None + } + } +} + +fn direct_super_traits(db: &dyn DefDatabase, trait_: TraitId, cb: impl FnMut(TraitId)) { let resolver = trait_.resolver(db); - // returning the iterator directly doesn't easily work because of - // lifetime problems, but since there usually shouldn't be more than a - // few direct traits this should be fine (we could even use some kind of - // SmallVec if performance is a concern) let generic_params = db.generic_params(trait_.into()); let trait_self = generic_params.find_trait_self_param(); generic_params @@ -73,18 +134,14 @@ fn direct_super_traits(db: &dyn DefDatabase, trait_: TraitId) -> SmallVec<[Trait Some(TypeNs::TraitId(t)) => Some(t), _ => None, }) - .collect() + .for_each(cb); } -fn direct_super_trait_refs(db: &dyn HirDatabase, trait_ref: &TraitRef) -> Vec<TraitRef> { - // returning the iterator directly doesn't easily work because of - // lifetime problems, but since there usually shouldn't be more than a - // few direct traits this should be fine (we could even use some kind of - // SmallVec if performance is a concern) +fn direct_super_trait_refs(db: &dyn HirDatabase, trait_ref: &TraitRef, cb: impl FnMut(TraitRef)) { let generic_params = db.generic_params(trait_ref.hir_trait_id().into()); let trait_self = match generic_params.find_trait_self_param() { Some(p) => TypeOrConstParamId { parent: trait_ref.hir_trait_id().into(), local_id: p }, - None => return Vec::new(), + None => return, }; db.generic_predicates_for_param(trait_self.parent, trait_self, None) .iter() @@ -100,64 +157,7 @@ fn direct_super_trait_refs(db: &dyn HirDatabase, trait_ref: &TraitRef) -> Vec<Tr }) }) .map(|pred| pred.substitute(Interner, &trait_ref.substitution)) - .collect() -} - -/// Returns an iterator over the whole super trait hierarchy (including the -/// trait itself). -pub fn all_super_traits(db: &dyn DefDatabase, trait_: TraitId) -> SmallVec<[TraitId; 4]> { - // we need to take care a bit here to avoid infinite loops in case of cycles - // (i.e. if we have `trait A: B; trait B: A;`) - - let mut result = smallvec![trait_]; - let mut i = 0; - while let Some(&t) = result.get(i) { - // yeah this is quadratic, but trait hierarchies should be flat - // enough that this doesn't matter - for tt in direct_super_traits(db, t) { - if !result.contains(&tt) { - result.push(tt); - } - } - i += 1; - } - result -} - -/// Given a trait ref (`Self: Trait`), builds all the implied trait refs for -/// super traits. The original trait ref will be included. So the difference to -/// `all_super_traits` is that we keep track of type parameters; for example if -/// we have `Self: Trait<u32, i32>` and `Trait<T, U>: OtherTrait<U>` we'll get -/// `Self: OtherTrait<i32>`. -pub(super) fn all_super_trait_refs(db: &dyn HirDatabase, trait_ref: TraitRef) -> SuperTraits<'_> { - SuperTraits { db, seen: iter::once(trait_ref.trait_id).collect(), stack: vec![trait_ref] } -} - -pub(super) struct SuperTraits<'a> { - db: &'a dyn HirDatabase, - stack: Vec<TraitRef>, - seen: FxHashSet<ChalkTraitId>, -} - -impl<'a> SuperTraits<'a> { - fn elaborate(&mut self, trait_ref: &TraitRef) { - let mut trait_refs = direct_super_trait_refs(self.db, trait_ref); - trait_refs.retain(|tr| !self.seen.contains(&tr.trait_id)); - self.stack.extend(trait_refs); - } -} - -impl<'a> Iterator for SuperTraits<'a> { - type Item = TraitRef; - - fn next(&mut self) -> Option<Self::Item> { - if let Some(next) = self.stack.pop() { - self.elaborate(&next); - Some(next) - } else { - None - } - } + .for_each(cb); } pub(super) fn associated_type_by_name_including_super_traits( @@ -165,7 +165,7 @@ pub(super) fn associated_type_by_name_including_super_traits( trait_ref: TraitRef, name: &Name, ) -> Option<(TraitRef, TypeAliasId)> { - all_super_trait_refs(db, trait_ref).find_map(|t| { + all_super_trait_refs(db, trait_ref, |t| { let assoc_type = db.trait_data(t.hir_trait_id()).associated_type_by_name(name)?; Some((t, assoc_type)) }) @@ -238,15 +238,18 @@ impl Generics { /// (parent total, self param, type param list, const param list, impl trait) pub(crate) fn provenance_split(&self) -> (usize, usize, usize, usize, usize) { - let ty_iter = || self.params.iter().filter_map(|x| x.1.type_param()); - - let self_params = - ty_iter().filter(|p| p.provenance == TypeParamProvenance::TraitSelf).count(); - let type_params = - ty_iter().filter(|p| p.provenance == TypeParamProvenance::TypeParamList).count(); - let impl_trait_params = - ty_iter().filter(|p| p.provenance == TypeParamProvenance::ArgumentImplTrait).count(); - let const_params = self.params.iter().filter_map(|x| x.1.const_param()).count(); + let mut self_params = 0; + let mut type_params = 0; + let mut impl_trait_params = 0; + let mut const_params = 0; + self.params.iter().for_each(|(_, data)| match data { + TypeOrConstParamData::TypeParamData(p) => match p.provenance { + TypeParamProvenance::TypeParamList => type_params += 1, + TypeParamProvenance::TraitSelf => self_params += 1, + TypeParamProvenance::ArgumentImplTrait => impl_trait_params += 1, + }, + TypeOrConstParamData::ConstParamData(_) => const_params += 1, + }); let parent_len = self.parent_generics().map_or(0, Generics::len); (parent_len, self_params, type_params, const_params, impl_trait_params) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/walk.rs b/src/tools/rust-analyzer/crates/hir-ty/src/walk.rs deleted file mode 100644 index c47689455..000000000 --- a/src/tools/rust-analyzer/crates/hir-ty/src/walk.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! The `TypeWalk` trait (probably to be replaced by Chalk's `Fold` and -//! `Visit`). - -use chalk_ir::interner::HasInterner; - -use crate::{ - AliasEq, AliasTy, Binders, CallableSig, FnSubst, GenericArg, GenericArgData, Interner, - OpaqueTy, ProjectionTy, Substitution, TraitRef, Ty, TyKind, WhereClause, -}; - -/// This allows walking structures that contain types to do something with those -/// types, similar to Chalk's `Fold` trait. -pub trait TypeWalk { - fn walk(&self, f: &mut impl FnMut(&Ty)); -} - -impl TypeWalk for Ty { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - match self.kind(Interner) { - TyKind::Alias(AliasTy::Projection(p_ty)) => { - for t in p_ty.substitution.iter(Interner) { - t.walk(f); - } - } - TyKind::Alias(AliasTy::Opaque(o_ty)) => { - for t in o_ty.substitution.iter(Interner) { - t.walk(f); - } - } - TyKind::Dyn(dyn_ty) => { - for p in dyn_ty.bounds.skip_binders().interned().iter() { - p.walk(f); - } - } - TyKind::Slice(ty) - | TyKind::Array(ty, _) - | TyKind::Ref(_, _, ty) - | TyKind::Raw(_, ty) => { - ty.walk(f); - } - TyKind::Function(fn_pointer) => { - fn_pointer.substitution.0.walk(f); - } - TyKind::Adt(_, substs) - | TyKind::FnDef(_, substs) - | TyKind::Tuple(_, substs) - | TyKind::OpaqueType(_, substs) - | TyKind::AssociatedType(_, substs) - | TyKind::Closure(.., substs) => { - substs.walk(f); - } - _ => {} - } - f(self); - } -} - -impl<T: TypeWalk> TypeWalk for Vec<T> { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - for t in self { - t.walk(f); - } - } -} - -impl TypeWalk for OpaqueTy { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - self.substitution.walk(f); - } -} - -impl TypeWalk for ProjectionTy { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - self.substitution.walk(f); - } -} - -impl TypeWalk for AliasTy { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - match self { - AliasTy::Projection(it) => it.walk(f), - AliasTy::Opaque(it) => it.walk(f), - } - } -} - -impl TypeWalk for GenericArg { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - if let GenericArgData::Ty(ty) = &self.interned() { - ty.walk(f); - } - } -} - -impl TypeWalk for Substitution { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - for t in self.iter(Interner) { - t.walk(f); - } - } -} - -impl<T: TypeWalk + HasInterner<Interner = Interner>> TypeWalk for Binders<T> { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - self.skip_binders().walk(f); - } -} - -impl TypeWalk for TraitRef { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - self.substitution.walk(f); - } -} - -impl TypeWalk for WhereClause { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - match self { - WhereClause::Implemented(trait_ref) => trait_ref.walk(f), - WhereClause::AliasEq(alias_eq) => alias_eq.walk(f), - _ => {} - } - } -} - -impl TypeWalk for CallableSig { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - for t in self.params_and_return.iter() { - t.walk(f); - } - } -} - -impl TypeWalk for AliasEq { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - self.ty.walk(f); - match &self.alias { - AliasTy::Projection(projection_ty) => projection_ty.walk(f), - AliasTy::Opaque(opaque) => opaque.walk(f), - } - } -} - -impl TypeWalk for FnSubst<Interner> { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - self.0.walk(f) - } -} diff --git a/src/tools/rust-analyzer/crates/hir/Cargo.toml b/src/tools/rust-analyzer/crates/hir/Cargo.toml index f780e3f53..ef40a8902 100644 --- a/src/tools/rust-analyzer/crates/hir/Cargo.toml +++ b/src/tools/rust-analyzer/crates/hir/Cargo.toml @@ -2,9 +2,11 @@ name = "hir" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -14,15 +16,16 @@ rustc-hash = "1.1.0" either = "1.7.0" arrayvec = "0.7.2" itertools = "0.10.5" -smallvec = "1.10.0" -once_cell = "1.15.0" +smallvec.workspace = true +once_cell = "1.17.0" -stdx = { path = "../stdx", version = "0.0.0" } -syntax = { path = "../syntax", version = "0.0.0" } -base-db = { path = "../base-db", version = "0.0.0" } -profile = { path = "../profile", version = "0.0.0" } -hir-expand = { path = "../hir-expand", version = "0.0.0" } -hir-def = { path = "../hir-def", version = "0.0.0" } -hir-ty = { path = "../hir-ty", version = "0.0.0" } -tt = { path = "../tt", version = "0.0.0" } -cfg = { path = "../cfg", version = "0.0.0" } +# local deps +base-db.workspace = true +cfg.workspace = true +hir-def.workspace = true +hir-expand.workspace = true +hir-ty.workspace = true +profile.workspace = true +stdx.workspace = true +syntax.workspace = true +tt.workspace = true diff --git a/src/tools/rust-analyzer/crates/hir/src/display.rs b/src/tools/rust-analyzer/crates/hir/src/display.rs index 5a4b2f334..0d1942012 100644 --- a/src/tools/rust-analyzer/crates/hir/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir/src/display.rs @@ -4,6 +4,7 @@ use hir_def::{ generics::{ TypeOrConstParamData, TypeParamProvenance, WherePredicate, WherePredicateTypeTarget, }, + lang_item::LangItem, type_ref::{TypeBound, TypeRef}, AdtId, GenericDefId, }; @@ -14,7 +15,6 @@ use hir_ty::{ }, Interner, TraitRefExt, WhereClause, }; -use syntax::SmolStr; use crate::{ Adt, Const, ConstParam, Enum, Field, Function, GenericParam, HasCrate, HasVisibility, @@ -261,8 +261,7 @@ impl HirDisplay for TypeParam { bounds.iter().cloned().map(|b| b.substitute(Interner, &substs)).collect(); let krate = self.id.parent().krate(f.db).id; let sized_trait = - f.db.lang_item(krate, SmolStr::new_inline("sized")) - .and_then(|lang_item| lang_item.as_trait()); + f.db.lang_item(krate, LangItem::Sized).and_then(|lang_item| lang_item.as_trait()); let has_only_sized_bound = predicates.iter().all(move |pred| match pred.skip_binders() { WhereClause::Implemented(it) => Some(it.hir_trait_id()) == sized_trait, _ => false, @@ -270,7 +269,7 @@ impl HirDisplay for TypeParam { let has_only_not_sized_bound = predicates.is_empty(); if !has_only_sized_bound || has_only_not_sized_bound { let default_sized = SizedByDefault::Sized { anchor: krate }; - write_bounds_like_dyn_trait_with_prefix(":", &predicates, default_sized, f)?; + write_bounds_like_dyn_trait_with_prefix(f, ":", &predicates, default_sized)?; } Ok(()) } diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 08fd4453d..2cb4ed2c3 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -44,12 +44,13 @@ use hir_def::{ expr::{BindingAnnotation, ExprOrPatId, LabelId, Pat, PatId}, generics::{TypeOrConstParamData, TypeParamProvenance}, item_tree::ItemTreeNode, - lang_item::LangItemTarget, + lang_item::{LangItem, LangItemTarget}, layout::{Layout, LayoutError, ReprOptions}, nameres::{self, diagnostics::DefDiagnostic}, per_ns::PerNs, resolver::{HasResolver, Resolver}, src::HasSource as _, + type_ref::ConstScalar, AdtId, AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, LifetimeParamId, LocalEnumVariantId, LocalFieldId, Lookup, MacroExpander, MacroId, ModuleId, StaticId, StructId, @@ -65,8 +66,9 @@ use hir_ty::{ primitive::UintTy, traits::FnTrait, AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId, - GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, Substitution, - TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, WhereClause, + ConcreteConst, ConstValue, GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, + Substitution, TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, + WhereClause, }; use itertools::Itertools; use nameres::diagnostics::DefDiagnosticKind; @@ -107,7 +109,7 @@ pub use { cfg::{CfgAtom, CfgExpr, CfgOptions}, hir_def::{ adt::StructKind, - attr::{Attr, Attrs, AttrsWithOwner, Documentation}, + attr::{Attrs, AttrsWithOwner, Documentation}, builtin_attr::AttributeTemplate, find_path::PrefixKind, import_map, @@ -122,11 +124,12 @@ pub use { ModuleDefId, }, hir_expand::{ + attrs::Attr, name::{known, Name}, ExpandResult, HirFileId, InFile, MacroFile, Origin, }, hir_ty::{ - display::{HirDisplay, HirWrite}, + display::{HirDisplay, HirDisplayError, HirWrite}, PointerCast, Safety, }, }; @@ -471,8 +474,8 @@ impl Module { let def_map = self.id.def_map(db.upcast()); let children = def_map[self.id.local_id] .children - .iter() - .map(|(_, module_id)| Module { id: def_map.module_id(*module_id) }) + .values() + .map(|module_id| Module { id: def_map.module_id(*module_id) }) .collect::<Vec<_>>(); children.into_iter() } @@ -784,7 +787,7 @@ fn precise_macro_call_location( let token = (|| { let derive_attr = node .doc_comments_and_attrs() - .nth(*derive_attr_index as usize) + .nth(derive_attr_index.ast_index()) .and_then(Either::left)?; let token_tree = derive_attr.meta()?.token_tree()?; let group_by = token_tree @@ -812,9 +815,11 @@ fn precise_macro_call_location( let node = ast_id.to_node(db.upcast()); let attr = node .doc_comments_and_attrs() - .nth((*invoc_attr_index) as usize) + .nth(invoc_attr_index.ast_index()) .and_then(Either::left) - .unwrap_or_else(|| panic!("cannot find attribute #{invoc_attr_index}")); + .unwrap_or_else(|| { + panic!("cannot find attribute #{}", invoc_attr_index.ast_index()) + }); ( ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&attr))), @@ -920,7 +925,7 @@ impl Struct { } pub fn repr(self, db: &dyn HirDatabase) -> Option<ReprOptions> { - db.struct_data(self.id).repr.clone() + db.struct_data(self.id).repr } pub fn kind(self, db: &dyn HirDatabase) -> StructKind { @@ -1831,7 +1836,7 @@ pub struct Trait { impl Trait { pub fn lang(db: &dyn HirDatabase, krate: Crate, name: &Name) -> Option<Trait> { - db.lang_item(krate.into(), name.to_smol_str()) + db.lang_item(krate.into(), LangItem::from_name(name)?) .and_then(LangItemTarget::as_trait) .map(Into::into) } @@ -2126,7 +2131,7 @@ pub enum AssocItem { Const(Const), TypeAlias(TypeAlias), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum AssocItemContainer { Trait(Trait), Impl(Impl), @@ -2160,6 +2165,16 @@ impl AsAssocItem for ModuleDef { } } } +impl AsAssocItem for DefWithBody { + fn as_assoc_item(self, db: &dyn HirDatabase) -> Option<AssocItem> { + match self { + DefWithBody::Function(it) => it.as_assoc_item(db), + DefWithBody::Const(it) => it.as_assoc_item(db), + DefWithBody::Static(_) | DefWithBody::Variant(_) => None, + } + } +} + fn as_assoc_item<ID, DEF, CTOR, AST>(db: &dyn HirDatabase, ctor: CTOR, id: ID) -> Option<AssocItem> where ID: Lookup<Data = AssocItemLoc<AST>>, @@ -2406,7 +2421,7 @@ impl Local { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct DeriveHelper { pub(crate) derive: MacroId, - pub(crate) idx: usize, + pub(crate) idx: u32, } impl DeriveHelper { @@ -2416,15 +2431,18 @@ impl DeriveHelper { pub fn name(&self, db: &dyn HirDatabase) -> Name { match self.derive { - MacroId::Macro2Id(it) => { - db.macro2_data(it).helpers.as_deref().and_then(|it| it.get(self.idx)).cloned() - } + MacroId::Macro2Id(it) => db + .macro2_data(it) + .helpers + .as_deref() + .and_then(|it| it.get(self.idx as usize)) + .cloned(), MacroId::MacroRulesId(_) => None, MacroId::ProcMacroId(proc_macro) => db .proc_macro_data(proc_macro) .helpers .as_deref() - .and_then(|it| it.get(self.idx)) + .and_then(|it| it.get(self.idx as usize)) .cloned(), } .unwrap_or_else(|| Name::missing()) @@ -2435,7 +2453,7 @@ impl DeriveHelper { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct BuiltinAttr { krate: Option<CrateId>, - idx: usize, + idx: u32, } impl BuiltinAttr { @@ -2444,7 +2462,8 @@ impl BuiltinAttr { if let builtin @ Some(_) = Self::builtin(name) { return builtin; } - let idx = db.crate_def_map(krate.id).registered_attrs().iter().position(|it| it == name)?; + let idx = + db.crate_def_map(krate.id).registered_attrs().iter().position(|it| it == name)? as u32; Some(BuiltinAttr { krate: Some(krate.id), idx }) } @@ -2452,21 +2471,21 @@ impl BuiltinAttr { hir_def::builtin_attr::INERT_ATTRIBUTES .iter() .position(|tool| tool.name == name) - .map(|idx| BuiltinAttr { krate: None, idx }) + .map(|idx| BuiltinAttr { krate: None, idx: idx as u32 }) } pub fn name(&self, db: &dyn HirDatabase) -> SmolStr { // FIXME: Return a `Name` here match self.krate { - Some(krate) => db.crate_def_map(krate).registered_attrs()[self.idx].clone(), - None => SmolStr::new(hir_def::builtin_attr::INERT_ATTRIBUTES[self.idx].name), + Some(krate) => db.crate_def_map(krate).registered_attrs()[self.idx as usize].clone(), + None => SmolStr::new(hir_def::builtin_attr::INERT_ATTRIBUTES[self.idx as usize].name), } } pub fn template(&self, _: &dyn HirDatabase) -> Option<AttributeTemplate> { match self.krate { Some(_) => None, - None => Some(hir_def::builtin_attr::INERT_ATTRIBUTES[self.idx].template), + None => Some(hir_def::builtin_attr::INERT_ATTRIBUTES[self.idx as usize].template), } } } @@ -2474,7 +2493,7 @@ impl BuiltinAttr { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct ToolModule { krate: Option<CrateId>, - idx: usize, + idx: u32, } impl ToolModule { @@ -2483,7 +2502,8 @@ impl ToolModule { if let builtin @ Some(_) = Self::builtin(name) { return builtin; } - let idx = db.crate_def_map(krate.id).registered_tools().iter().position(|it| it == name)?; + let idx = + db.crate_def_map(krate.id).registered_tools().iter().position(|it| it == name)? as u32; Some(ToolModule { krate: Some(krate.id), idx }) } @@ -2491,14 +2511,14 @@ impl ToolModule { hir_def::builtin_attr::TOOL_MODULES .iter() .position(|&tool| tool == name) - .map(|idx| ToolModule { krate: None, idx }) + .map(|idx| ToolModule { krate: None, idx: idx as u32 }) } pub fn name(&self, db: &dyn HirDatabase) -> SmolStr { // FIXME: Return a `Name` here match self.krate { - Some(krate) => db.crate_def_map(krate).registered_tools()[self.idx].clone(), - None => SmolStr::new(hir_def::builtin_attr::TOOL_MODULES[self.idx]), + Some(krate) => db.crate_def_map(krate).registered_tools()[self.idx as usize].clone(), + None => SmolStr::new(hir_def::builtin_attr::TOOL_MODULES[self.idx as usize]), } } } @@ -2555,6 +2575,14 @@ impl GenericParam { GenericParam::LifetimeParam(it) => it.name(db), } } + + pub fn parent(self) -> GenericDef { + match self { + GenericParam::TypeParam(it) => it.id.parent().into(), + GenericParam::ConstParam(it) => it.id.parent().into(), + GenericParam::LifetimeParam(it) => it.id.parent.into(), + } + } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -2788,14 +2816,19 @@ impl Impl { all } - // FIXME: the return type is wrong. This should be a hir version of - // `TraitRef` (to account for parameters and qualifiers) pub fn trait_(self, db: &dyn HirDatabase) -> Option<Trait> { - let trait_ref = db.impl_trait(self.id)?.skip_binders().clone(); - let id = hir_ty::from_chalk_trait_id(trait_ref.trait_id); + let trait_ref = db.impl_trait(self.id)?; + let id = trait_ref.skip_binders().hir_trait_id(); Some(Trait { id }) } + pub fn trait_ref(self, db: &dyn HirDatabase) -> Option<TraitRef> { + let substs = TyBuilder::placeholder_subst(db, self.id); + let trait_ref = db.impl_trait(self.id)?.substitute(Interner, &substs); + let resolver = self.id.resolver(db.upcast()); + Some(TraitRef::new_with_resolver(db, &resolver, trait_ref)) + } + pub fn self_ty(self, db: &dyn HirDatabase) -> Type { let resolver = self.id.resolver(db.upcast()); let substs = TyBuilder::placeholder_subst(db, self.id); @@ -2821,6 +2854,48 @@ impl Impl { } } +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub struct TraitRef { + env: Arc<TraitEnvironment>, + trait_ref: hir_ty::TraitRef, +} + +impl TraitRef { + pub(crate) fn new_with_resolver( + db: &dyn HirDatabase, + resolver: &Resolver, + trait_ref: hir_ty::TraitRef, + ) -> TraitRef { + let env = resolver.generic_def().map_or_else( + || Arc::new(TraitEnvironment::empty(resolver.krate())), + |d| db.trait_environment(d), + ); + TraitRef { env, trait_ref } + } + + pub fn trait_(&self) -> Trait { + let id = self.trait_ref.hir_trait_id(); + Trait { id } + } + + pub fn self_ty(&self) -> Type { + let ty = self.trait_ref.self_type_parameter(Interner); + Type { env: self.env.clone(), ty } + } + + /// Returns `idx`-th argument of this trait reference if it is a type argument. Note that the + /// first argument is the `Self` type. + pub fn get_type_argument(&self, idx: usize) -> Option<Type> { + self.trait_ref + .substitution + .as_slice(Interner) + .get(idx) + .and_then(|arg| arg.ty(Interner)) + .cloned() + .map(|ty| Type { env: self.env.clone(), ty }) + } +} + #[derive(Clone, PartialEq, Eq, Debug)] pub struct Type { env: Arc<TraitEnvironment>, @@ -2957,7 +3032,7 @@ impl Type { /// This function is used in `.await` syntax completion. pub fn impls_into_future(&self, db: &dyn HirDatabase) -> bool { let trait_ = db - .lang_item(self.env.krate, SmolStr::new_inline("into_future")) + .lang_item(self.env.krate, LangItem::IntoFutureIntoFuture) .and_then(|it| { let into_future_fn = it.as_function()?; let assoc_item = as_assoc_item(db, AssocItem::Function, into_future_fn)?; @@ -2965,8 +3040,7 @@ impl Type { Some(into_future_trait.id) }) .or_else(|| { - let future_trait = - db.lang_item(self.env.krate, SmolStr::new_inline("future_trait"))?; + let future_trait = db.lang_item(self.env.krate, LangItem::Future)?; future_trait.as_trait() }); @@ -3059,9 +3133,9 @@ impl Type { } pub fn is_copy(&self, db: &dyn HirDatabase) -> bool { - let lang_item = db.lang_item(self.env.krate, SmolStr::new_inline("copy")); + let lang_item = db.lang_item(self.env.krate, LangItem::Copy); let copy_trait = match lang_item { - Some(LangItemTarget::TraitId(it)) => it, + Some(LangItemTarget::Trait(it)) => it, _ => return false, }; self.impls_trait(db, copy_trait.into(), &[]) @@ -3088,15 +3162,15 @@ impl Type { } pub fn is_closure(&self) -> bool { - matches!(&self.ty.kind(Interner), TyKind::Closure { .. }) + matches!(self.ty.kind(Interner), TyKind::Closure { .. }) } pub fn is_fn(&self) -> bool { - matches!(&self.ty.kind(Interner), TyKind::FnDef(..) | TyKind::Function { .. }) + matches!(self.ty.kind(Interner), TyKind::FnDef(..) | TyKind::Function { .. }) } pub fn is_array(&self) -> bool { - matches!(&self.ty.kind(Interner), TyKind::Array(..)) + matches!(self.ty.kind(Interner), TyKind::Array(..)) } pub fn is_packed(&self, db: &dyn HirDatabase) -> bool { @@ -3113,10 +3187,12 @@ impl Type { } pub fn is_raw_ptr(&self) -> bool { - matches!(&self.ty.kind(Interner), TyKind::Raw(..)) + matches!(self.ty.kind(Interner), TyKind::Raw(..)) } pub fn contains_unknown(&self) -> bool { + // FIXME: When we get rid of `ConstScalar::Unknown`, we can just look at precomputed + // `TypeFlags` in `TyData`. return go(&self.ty); fn go(ty: &Ty) -> bool { @@ -3182,6 +3258,19 @@ impl Type { } } + pub fn as_array(&self, _db: &dyn HirDatabase) -> Option<(Type, usize)> { + if let TyKind::Array(ty, len) = &self.ty.kind(Interner) { + match len.data(Interner).value { + ConstValue::Concrete(ConcreteConst { interned: ConstScalar::UInt(len) }) => { + Some((self.derived(ty.clone()), len as usize)) + } + _ => None, + } + } else { + None + } + } + pub fn autoderef<'a>(&'a self, db: &'a dyn HirDatabase) -> impl Iterator<Item = Type> + 'a { self.autoderef_(db).map(move |ty| self.derived(ty)) } @@ -3418,10 +3507,9 @@ impl Type { Type { env: self.env.clone(), ty } } + /// Visits every type, including generic arguments, in this type. `cb` is called with type + /// itself first, and then with its generic arguments. pub fn walk(&self, db: &dyn HirDatabase, mut cb: impl FnMut(Type)) { - // TypeWalk::walk for a Ty at first visits parameters and only after that the Ty itself. - // We need a different order here. - fn walk_substs( db: &dyn HirDatabase, type_: &Type, @@ -3534,6 +3622,14 @@ impl Type { _ => None, } } + + /// Returns unique `GenericParam`s contained in this type. + pub fn generic_params(&self, db: &dyn HirDatabase) -> FxHashSet<GenericParam> { + hir_ty::collect_placeholders(&self.ty, db) + .into_iter() + .map(|id| TypeOrConstParam { id }.split(db).either_into()) + .collect() + } } #[derive(Debug)] diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index e0d261039..486b7ee62 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -1319,10 +1319,7 @@ impl<'db> SemanticsImpl<'db> { let _p = profile::span("Semantics::analyze_impl"); let node = self.find_file(node); - let container = match self.with_ctx(|ctx| ctx.find_container(node)) { - Some(it) => it, - None => return None, - }; + let container = self.with_ctx(|ctx| ctx.find_container(node))?; let resolver = match container { ChildContainer::DefWithBodyId(def) => { @@ -1472,14 +1469,7 @@ impl<'db> SemanticsImpl<'db> { } fn is_inside_unsafe(&self, expr: &ast::Expr) -> bool { - let item_or_variant = |ancestor: SyntaxNode| { - if ast::Item::can_cast(ancestor.kind()) { - ast::Item::cast(ancestor).map(Either::Left) - } else { - ast::Variant::cast(ancestor).map(Either::Right) - } - }; - let Some(enclosing_item) = expr.syntax().ancestors().find_map(item_or_variant) else { return false }; + let Some(enclosing_item) = expr.syntax().ancestors().find_map(Either::<ast::Item, ast::Variant>::cast) else { return false }; let def = match &enclosing_item { Either::Left(ast::Item::Fn(it)) if it.unsafe_token().is_some() => return true, @@ -1589,7 +1579,7 @@ fn find_root(node: &SyntaxNode) -> SyntaxNode { node.ancestors().last().unwrap() } -/// `SemanticScope` encapsulates the notion of a scope (the set of visible +/// `SemanticsScope` encapsulates the notion of a scope (the set of visible /// names) at a particular program point. /// /// It is a bit tricky, as scopes do not really exist inside the compiler. diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs index fa45e3c12..2b5bfda1d 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs @@ -87,7 +87,6 @@ use base_db::FileId; use hir_def::{ - attr::AttrId, child_by_source::ChildBySource, dyn_map::DynMap, expr::{LabelId, PatId}, @@ -96,7 +95,7 @@ use hir_def::{ GenericDefId, GenericParamId, ImplId, LifetimeParamId, MacroId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, UnionId, VariantId, }; -use hir_expand::{name::AsName, HirFileId, MacroCallId}; +use hir_expand::{attrs::AttrId, name::AsName, HirFileId, MacroCallId}; use rustc_hash::FxHashMap; use smallvec::SmallVec; use stdx::impl_from; diff --git a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs index 059b80bcf..3b39e9fa9 100644 --- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs +++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs @@ -17,6 +17,7 @@ use hir_def::{ Body, BodySourceMap, }, expr::{ExprId, Pat, PatId}, + lang_item::LangItem, macro_id_to_def_id, path::{ModPath, Path, PathKind}, resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs}, @@ -37,7 +38,7 @@ use hir_ty::{ record_literal_missing_fields, record_pattern_missing_fields, unsafe_expressions, UnsafeExpr, }, - method_resolution::{self, lang_names_for_bin_op}, + method_resolution::{self, lang_items_for_bin_op}, Adjustment, InferenceResult, Interner, Substitution, Ty, TyExt, TyKind, TyLoweringContext, }; use itertools::Itertools; @@ -294,12 +295,8 @@ impl SourceAnalyzer { } } - let future_trait = db - .lang_item(self.resolver.krate(), hir_expand::name![future_trait].to_smol_str())? - .as_trait()?; - let poll_fn = db - .lang_item(self.resolver.krate(), hir_expand::name![poll].to_smol_str())? - .as_function()?; + let future_trait = db.lang_item(self.resolver.krate(), LangItem::Future)?.as_trait()?; + let poll_fn = db.lang_item(self.resolver.krate(), LangItem::FuturePoll)?.as_function()?; // HACK: subst for `poll()` coincides with that for `Future` because `poll()` itself // doesn't have any generic parameters, so we skip building another subst for `poll()`. let substs = hir_ty::TyBuilder::subst_for_def(db, future_trait, None).push(ty).build(); @@ -311,14 +308,14 @@ impl SourceAnalyzer { db: &dyn HirDatabase, prefix_expr: &ast::PrefixExpr, ) -> Option<FunctionId> { - let lang_item_name = match prefix_expr.op_kind()? { - ast::UnaryOp::Deref => name![deref], - ast::UnaryOp::Not => name![not], - ast::UnaryOp::Neg => name![neg], + let (lang_item, fn_name) = match prefix_expr.op_kind()? { + ast::UnaryOp::Deref => (LangItem::Deref, name![deref]), + ast::UnaryOp::Not => (LangItem::Not, name![not]), + ast::UnaryOp::Neg => (LangItem::Neg, name![neg]), }; let ty = self.ty_of_expr(db, &prefix_expr.expr()?)?; - let (op_trait, op_fn) = self.lang_trait_fn(db, &lang_item_name, &lang_item_name)?; + let (op_trait, op_fn) = self.lang_trait_fn(db, lang_item, &fn_name)?; // HACK: subst for all methods coincides with that for their trait because the methods // don't have any generic parameters, so we skip building another subst for the methods. let substs = hir_ty::TyBuilder::subst_for_def(db, op_trait, None).push(ty.clone()).build(); @@ -334,9 +331,7 @@ impl SourceAnalyzer { let base_ty = self.ty_of_expr(db, &index_expr.base()?)?; let index_ty = self.ty_of_expr(db, &index_expr.index()?)?; - let lang_item_name = name![index]; - - let (op_trait, op_fn) = self.lang_trait_fn(db, &lang_item_name, &lang_item_name)?; + let (op_trait, op_fn) = self.lang_trait_fn(db, LangItem::Index, &name![index])?; // HACK: subst for all methods coincides with that for their trait because the methods // don't have any generic parameters, so we skip building another subst for the methods. let substs = hir_ty::TyBuilder::subst_for_def(db, op_trait, None) @@ -355,8 +350,8 @@ impl SourceAnalyzer { let lhs = self.ty_of_expr(db, &binop_expr.lhs()?)?; let rhs = self.ty_of_expr(db, &binop_expr.rhs()?)?; - let (op_trait, op_fn) = lang_names_for_bin_op(op) - .and_then(|(name, lang_item)| self.lang_trait_fn(db, &lang_item, &name))?; + let (op_trait, op_fn) = lang_items_for_bin_op(op) + .and_then(|(name, lang_item)| self.lang_trait_fn(db, lang_item, &name))?; // HACK: subst for `index()` coincides with that for `Index` because `index()` itself // doesn't have any generic parameters, so we skip building another subst for `index()`. let substs = hir_ty::TyBuilder::subst_for_def(db, op_trait, None) @@ -374,8 +369,7 @@ impl SourceAnalyzer { ) -> Option<FunctionId> { let ty = self.ty_of_expr(db, &try_expr.expr()?)?; - let op_fn = - db.lang_item(self.resolver.krate(), name![branch].to_smol_str())?.as_function()?; + let op_fn = db.lang_item(self.resolver.krate(), LangItem::TryTraitBranch)?.as_function()?; let op_trait = match op_fn.lookup(db.upcast()).container { ItemContainerId::TraitId(id) => id, _ => return None, @@ -504,7 +498,7 @@ impl SourceAnalyzer { AssocItemId::ConstId(const_id) => { self.resolve_impl_const_or_trait_def(db, const_id, subs).into() } - _ => assoc, + assoc => assoc, }; return Some(PathResolution::Def(AssocItem::from(assoc).into())); @@ -517,7 +511,13 @@ impl SourceAnalyzer { prefer_value_ns = true; } else if let Some(path_pat) = parent().and_then(ast::PathPat::cast) { let pat_id = self.pat_id(&path_pat.into())?; - if let Some((assoc, _)) = infer.assoc_resolutions_for_pat(pat_id) { + if let Some((assoc, subs)) = infer.assoc_resolutions_for_pat(pat_id) { + let assoc = match assoc { + AssocItemId::ConstId(const_id) => { + self.resolve_impl_const_or_trait_def(db, const_id, subs).into() + } + assoc => assoc, + }; return Some(PathResolution::Def(AssocItem::from(assoc).into())); } if let Some(VariantId::EnumVariantId(variant)) = @@ -628,7 +628,7 @@ impl SourceAnalyzer { { return Some(PathResolution::DeriveHelper(DeriveHelper { derive: *macro_id, - idx, + idx: idx as u32, })); } } @@ -815,10 +815,10 @@ impl SourceAnalyzer { fn lang_trait_fn( &self, db: &dyn HirDatabase, - lang_trait: &Name, + lang_trait: LangItem, method_name: &Name, ) -> Option<(TraitId, FunctionId)> { - let trait_id = db.lang_item(self.resolver.krate(), lang_trait.to_smol_str())?.as_trait()?; + let trait_id = db.lang_item(self.resolver.krate(), lang_trait)?.as_trait()?; let fn_id = db.trait_data(trait_id).method_by_name(method_name)?; Some((trait_id, fn_id)) } diff --git a/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml b/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml index b9260473b..447e38f91 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml +++ b/src/tools/rust-analyzer/crates/ide-assists/Cargo.toml @@ -2,9 +2,11 @@ name = "ide-assists" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -14,19 +16,22 @@ cov-mark = "2.0.0-pre.1" itertools = "0.10.5" either = "1.7.0" -smallvec = "1.10.0" +smallvec.workspace = true -stdx = { path = "../stdx", version = "0.0.0" } -syntax = { path = "../syntax", version = "0.0.0" } -text-edit = { path = "../text-edit", version = "0.0.0" } -profile = { path = "../profile", version = "0.0.0" } -ide-db = { path = "../ide-db", version = "0.0.0" } -hir = { path = "../hir", version = "0.0.0" } +# local deps +stdx.workspace = true +syntax.workspace = true +text-edit.workspace = true +profile.workspace = true +ide-db.workspace = true +hir.workspace = true [dev-dependencies] -test-utils = { path = "../test-utils" } -sourcegen = { path = "../sourcegen" } expect-test = "1.4.0" +# local deps +test-utils.workspace = true +sourcegen.workspace = true + [features] in-rust-tree = [] diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_braces.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_braces.rs new file mode 100644 index 000000000..2f4a263ee --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_braces.rs @@ -0,0 +1,155 @@ +use syntax::{ + ast::{self, edit::AstNodeEdit, make}, + AstNode, +}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: add_braces +// +// Adds braces to lambda and match arm expressions. +// +// ``` +// fn foo(n: i32) -> i32 { +// match n { +// 1 =>$0 n + 1, +// _ => 0 +// } +// } +// ``` +// -> +// ``` +// fn foo(n: i32) -> i32 { +// match n { +// 1 => { +// n + 1 +// }, +// _ => 0 +// } +// } +// ``` +pub(crate) fn add_braces(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let (expr_type, expr) = get_replacement_node(ctx)?; + + acc.add( + AssistId("add_braces", AssistKind::RefactorRewrite), + match expr_type { + ParentType::ClosureExpr => "Add braces to closure body", + ParentType::MatchArmExpr => "Add braces to arm expression", + }, + expr.syntax().text_range(), + |builder| { + let block_expr = AstNodeEdit::indent( + &make::block_expr(None, Some(expr.clone())), + AstNodeEdit::indent_level(&expr), + ); + + builder.replace(expr.syntax().text_range(), block_expr.syntax().text()); + }, + ) +} + +enum ParentType { + MatchArmExpr, + ClosureExpr, +} + +fn get_replacement_node(ctx: &AssistContext<'_>) -> Option<(ParentType, ast::Expr)> { + if let Some(match_arm) = ctx.find_node_at_offset::<ast::MatchArm>() { + let match_arm_expr = match_arm.expr()?; + + if matches!(match_arm_expr, ast::Expr::BlockExpr(_)) { + return None; + } + + return Some((ParentType::MatchArmExpr, match_arm_expr)); + } else if let Some(closure_expr) = ctx.find_node_at_offset::<ast::ClosureExpr>() { + let body = closure_expr.body()?; + + if matches!(body, ast::Expr::BlockExpr(_)) { + return None; + } + + return Some((ParentType::ClosureExpr, body)); + } + + None +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn suggest_add_braces_for_closure() { + check_assist( + add_braces, + r#" +fn foo() { + t(|n|$0 n + 100); +} +"#, + r#" +fn foo() { + t(|n| { + n + 100 + }); +} +"#, + ); + } + + #[test] + fn no_assist_for_closures_with_braces() { + check_assist_not_applicable( + add_braces, + r#" +fn foo() { + t(|n|$0 { n + 100 }); +} +"#, + ); + } + + #[test] + fn suggest_add_braces_for_match() { + check_assist( + add_braces, + r#" +fn foo() { + match n { + Some(n) $0=> 29, + _ => () + }; +} +"#, + r#" +fn foo() { + match n { + Some(n) => { + 29 + }, + _ => () + }; +} +"#, + ); + } + + #[test] + fn no_assist_for_match_with_braces() { + check_assist_not_applicable( + add_braces, + r#" +fn foo() { + match n { + Some(n) $0=> { return 29; }, + _ => () + }; +} +"#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs index 161bcc5c8..4e11b31de 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -1,7 +1,5 @@ use hir::HasSource; -use ide_db::{ - syntax_helpers::insert_whitespace_into_node::insert_ws_into, traits::resolve_target_trait, -}; +use ide_db::syntax_helpers::insert_whitespace_into_node::insert_ws_into; use syntax::ast::{self, make, AstNode}; use crate::{ @@ -107,16 +105,19 @@ fn add_missing_impl_members_inner( ) -> Option<()> { let _p = profile::span("add_missing_impl_members_inner"); let impl_def = ctx.find_node_at_offset::<ast::Impl>()?; + let impl_ = ctx.sema.to_def(&impl_def)?; if ctx.token_at_offset().all(|t| { t.parent_ancestors() + .take_while(|node| node != impl_def.syntax()) .any(|s| ast::BlockExpr::can_cast(s.kind()) || ast::ParamList::can_cast(s.kind())) }) { return None; } let target_scope = ctx.sema.scope(impl_def.syntax())?; - let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; + let trait_ref = impl_.trait_ref(ctx.db())?; + let trait_ = trait_ref.trait_(); let missing_items = filter_assoc_items( &ctx.sema, @@ -155,7 +156,7 @@ fn add_missing_impl_members_inner( let placeholder; if let DefaultMethods::No = mode { if let ast::AssocItem::Fn(func) = &first_new_item { - if try_gen_trait_body(ctx, func, &trait_, &impl_def).is_none() { + if try_gen_trait_body(ctx, func, trait_ref, &impl_def).is_none() { if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) { @@ -180,13 +181,13 @@ fn add_missing_impl_members_inner( fn try_gen_trait_body( ctx: &AssistContext<'_>, func: &ast::Fn, - trait_: &hir::Trait, + trait_ref: hir::TraitRef, impl_def: &ast::Impl, ) -> Option<()> { - let trait_path = make::ext::ident_path(&trait_.name(ctx.db()).to_string()); + let trait_path = make::ext::ident_path(&trait_ref.trait_().name(ctx.db()).to_string()); let hir_ty = ctx.sema.resolve_type(&impl_def.self_ty()?)?; let adt = hir_ty.as_adt()?.source(ctx.db())?; - gen_trait_fn_body(func, &trait_path, &adt.value) + gen_trait_fn_body(func, &trait_path, &adt.value, Some(trait_ref)) } #[cfg(test)] @@ -1353,6 +1354,50 @@ impl PartialEq for SomeStruct { } #[test] + fn test_partial_eq_body_when_types_semantically_match() { + check_assist( + add_missing_impl_members, + r#" +//- minicore: eq +struct S<T, U>(T, U); +type Alias<T> = S<T, T>; +impl<T> PartialEq<Alias<T>> for S<T, T> {$0} +"#, + r#" +struct S<T, U>(T, U); +type Alias<T> = S<T, T>; +impl<T> PartialEq<Alias<T>> for S<T, T> { + $0fn eq(&self, other: &Alias<T>) -> bool { + self.0 == other.0 && self.1 == other.1 + } +} +"#, + ); + } + + #[test] + fn test_partial_eq_body_when_types_dont_match() { + check_assist( + add_missing_impl_members, + r#" +//- minicore: eq +struct S<T, U>(T, U); +type Alias<T> = S<T, T>; +impl<T> PartialEq<Alias<T>> for S<T, i32> {$0} +"#, + r#" +struct S<T, U>(T, U); +type Alias<T> = S<T, T>; +impl<T> PartialEq<Alias<T>> for S<T, i32> { + fn eq(&self, other: &Alias<T>) -> bool { + ${0:todo!()} + } +} +"#, + ); + } + + #[test] fn test_ignore_function_body() { check_assist_not_applicable( add_missing_default_members, @@ -1442,4 +1487,35 @@ impl Trait for () { }"#, ) } + + #[test] + fn test_works_inside_function() { + check_assist( + add_missing_impl_members, + r#" +trait Tr { + fn method(); +} +fn main() { + struct S; + impl Tr for S { + $0 + } +} +"#, + r#" +trait Tr { + fn method(); +} +fn main() { + struct S; + impl Tr for S { + fn method() { + ${0:todo!()} + } + } +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs index 8e4ac69ae..5d81e8cfe 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs @@ -140,6 +140,31 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) }) .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat)); ((Box::new(missing_pats) as Box<dyn Iterator<Item = _>>).peekable(), is_non_exhaustive) + } else if let Some((enum_def, len)) = resolve_array_of_enum_def(&ctx.sema, &expr) { + let is_non_exhaustive = enum_def.is_non_exhaustive(ctx.db(), module.krate()); + let variants = enum_def.variants(ctx.db()); + + if len.pow(variants.len() as u32) > 256 { + return None; + } + + let variants_of_enums = vec![variants.clone(); len]; + + let missing_pats = variants_of_enums + .into_iter() + .multi_cartesian_product() + .inspect(|_| cov_mark::hit!(add_missing_match_arms_lazy_computation)) + .map(|variants| { + let is_hidden = variants + .iter() + .any(|variant| variant.should_be_hidden(ctx.db(), module.krate())); + let patterns = variants.into_iter().filter_map(|variant| { + build_pat(ctx.db(), module, variant.clone(), ctx.config.prefer_no_std) + }); + (ast::Pat::from(make::slice_pat(patterns)), is_hidden) + }) + .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat)); + ((Box::new(missing_pats) as Box<dyn Iterator<Item = _>>).peekable(), is_non_exhaustive) } else { return None; }; @@ -266,9 +291,13 @@ fn is_variant_missing(existing_pats: &[Pat], var: &Pat) -> bool { fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool { match (pat, var) { (Pat::WildcardPat(_), _) => true, + (Pat::SlicePat(spat), Pat::SlicePat(svar)) => { + spat.pats().zip(svar.pats()).all(|(p, v)| does_pat_match_variant(&p, &v)) + } (Pat::TuplePat(tpat), Pat::TuplePat(tvar)) => { tpat.fields().zip(tvar.fields()).all(|(p, v)| does_pat_match_variant(&p, &v)) } + (Pat::OrPat(opat), _) => opat.pats().any(|p| does_pat_match_variant(&p, var)), _ => utils::does_pat_match_variant(pat, var), } } @@ -279,7 +308,7 @@ enum ExtendedEnum { Enum(hir::Enum), } -#[derive(Eq, PartialEq, Clone, Copy)] +#[derive(Eq, PartialEq, Clone, Copy, Debug)] enum ExtendedVariant { True, False, @@ -339,15 +368,30 @@ fn resolve_tuple_of_enum_def( .tuple_fields(sema.db) .iter() .map(|ty| { - ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() { - Some(Adt::Enum(e)) => Some(lift_enum(e)), - // For now we only handle expansion for a tuple of enums. Here - // we map non-enum items to None and rely on `collect` to - // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>. - _ => ty.is_bool().then_some(ExtendedEnum::Bool), + ty.autoderef(sema.db).find_map(|ty| { + match ty.as_adt() { + Some(Adt::Enum(e)) => Some(lift_enum(e)), + // For now we only handle expansion for a tuple of enums. Here + // we map non-enum items to None and rely on `collect` to + // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>. + _ => ty.is_bool().then_some(ExtendedEnum::Bool), + } }) }) - .collect() + .collect::<Option<Vec<ExtendedEnum>>>() + .and_then(|list| if list.is_empty() { None } else { Some(list) }) +} + +fn resolve_array_of_enum_def( + sema: &Semantics<'_, RootDatabase>, + expr: &ast::Expr, +) -> Option<(ExtendedEnum, usize)> { + sema.type_of_expr(expr)?.adjusted().as_array(sema.db).and_then(|(ty, len)| { + ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() { + Some(Adt::Enum(e)) => Some((lift_enum(e), len)), + _ => ty.is_bool().then_some((ExtendedEnum::Bool, len)), + }) + }) } fn build_pat( @@ -376,7 +420,6 @@ fn build_pat( } ast::StructKind::Unit => make::path_pat(path), }; - Some(pat) } ExtendedVariant::True => Some(ast::Pat::from(make::literal_pat("true"))), @@ -526,6 +569,19 @@ fn foo(a: bool) { r#" fn foo(a: bool) { match (a, a)$0 { + (true | false, true) => {} + (true, false) => {} + (false, false) => {} + } +} +"#, + ); + + check_assist_not_applicable( + add_missing_match_arms, + r#" +fn foo(a: bool) { + match (a, a)$0 { (true, true) => {} (true, false) => {} (false, true) => {} @@ -560,12 +616,112 @@ fn foo(a: bool) { } #[test] + fn fill_boolean_array() { + check_assist( + add_missing_match_arms, + r#" +fn foo(a: bool) { + match [a]$0 { + } +} +"#, + r#" +fn foo(a: bool) { + match [a] { + $0[true] => todo!(), + [false] => todo!(), + } +} +"#, + ); + + check_assist( + add_missing_match_arms, + r#" +fn foo(a: bool) { + match [a,]$0 { + } +} +"#, + r#" +fn foo(a: bool) { + match [a,] { + $0[true] => todo!(), + [false] => todo!(), + } +} +"#, + ); + + check_assist( + add_missing_match_arms, + r#" +fn foo(a: bool) { + match [a, a]$0 { + [true, true] => todo!(), + } +} +"#, + r#" +fn foo(a: bool) { + match [a, a] { + [true, true] => todo!(), + $0[true, false] => todo!(), + [false, true] => todo!(), + [false, false] => todo!(), + } +} +"#, + ); + + check_assist( + add_missing_match_arms, + r#" +fn foo(a: bool) { + match [a, a]$0 { + } +} +"#, + r#" +fn foo(a: bool) { + match [a, a] { + $0[true, true] => todo!(), + [true, false] => todo!(), + [false, true] => todo!(), + [false, false] => todo!(), + } +} +"#, + ) + } + + #[test] fn partial_fill_boolean_tuple() { check_assist( add_missing_match_arms, r#" fn foo(a: bool) { match (a, a)$0 { + (true | false, true) => {} + } +} +"#, + r#" +fn foo(a: bool) { + match (a, a) { + (true | false, true) => {} + $0(true, false) => todo!(), + (false, false) => todo!(), + } +} +"#, + ); + + check_assist( + add_missing_match_arms, + r#" +fn foo(a: bool) { + match (a, a)$0 { (false, true) => {} } } @@ -882,6 +1038,33 @@ fn main() { } "#, ); + + check_assist( + add_missing_match_arms, + r#" +enum E { A, B, C } +fn main() { + use E::*; + match (A, B, C)$0 { + (A | B , A, A | B | C) => (), + (A | B | C , B | C, A | B | C) => (), + } +} +"#, + r#" +enum E { A, B, C } +fn main() { + use E::*; + match (A, B, C) { + (A | B , A, A | B | C) => (), + (A | B | C , B | C, A | B | C) => (), + $0(C, A, A) => todo!(), + (C, A, B) => todo!(), + (C, A, C) => todo!(), + } +} +"#, + ) } #[test] diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs index 312cb65ab..1acd5ee97 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_block.rs @@ -107,7 +107,7 @@ fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> { /// The line -> block assist can be invoked from anywhere within a sequence of line comments. /// relevant_line_comments crawls backwards and forwards finding the complete sequence of comments that will /// be joined. -fn relevant_line_comments(comment: &ast::Comment) -> Vec<Comment> { +pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec<Comment> { // The prefix identifies the kind of comment we're dealing with let prefix = comment.prefix(); let same_prefix = |c: &ast::Comment| c.prefix() == prefix; @@ -159,7 +159,7 @@ fn relevant_line_comments(comment: &ast::Comment) -> Vec<Comment> { // */ // // But since such comments aren't idiomatic we're okay with this. -fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> String { +pub(crate) fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> String { let contents_without_prefix = comm.text().strip_prefix(comm.prefix()).unwrap(); let contents = contents_without_prefix.strip_prefix(' ').unwrap_or(contents_without_prefix); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs index 5bf04a3ad..65c2479e9 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs @@ -30,24 +30,23 @@ use crate::{ // ``` pub(crate) fn convert_match_to_let_else(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let let_stmt: ast::LetStmt = ctx.find_node_at_offset()?; - let binding = find_binding(let_stmt.pat()?)?; + let binding = let_stmt.pat()?; - let initializer = match let_stmt.initializer() { - Some(ast::Expr::MatchExpr(it)) => it, - _ => return None, - }; + let Some(ast::Expr::MatchExpr(initializer)) = let_stmt.initializer() else { return None }; let initializer_expr = initializer.expr()?; - let (extracting_arm, diverging_arm) = match find_arms(ctx, &initializer) { - Some(it) => it, - None => return None, - }; + let Some((extracting_arm, diverging_arm)) = find_arms(ctx, &initializer) else { return None }; if extracting_arm.guard().is_some() { cov_mark::hit!(extracting_arm_has_guard); return None; } - let diverging_arm_expr = diverging_arm.expr()?; + let diverging_arm_expr = match diverging_arm.expr()? { + ast::Expr::BlockExpr(block) if block.modifier().is_none() && block.label().is_none() => { + block.to_string() + } + other => format!("{{ {other} }}"), + }; let extracting_arm_pat = extracting_arm.pat()?; let extracted_variable = find_extracted_variable(ctx, &extracting_arm)?; @@ -56,24 +55,16 @@ pub(crate) fn convert_match_to_let_else(acc: &mut Assists, ctx: &AssistContext<' "Convert match to let-else", let_stmt.syntax().text_range(), |builder| { - let extracting_arm_pat = rename_variable(&extracting_arm_pat, extracted_variable, binding); + let extracting_arm_pat = + rename_variable(&extracting_arm_pat, extracted_variable, binding); builder.replace( let_stmt.syntax().text_range(), - format!("let {extracting_arm_pat} = {initializer_expr} else {{ {diverging_arm_expr} }};") + format!("let {extracting_arm_pat} = {initializer_expr} else {diverging_arm_expr};"), ) }, ) } -// Given a pattern, find the name introduced to the surrounding scope. -fn find_binding(pat: ast::Pat) -> Option<ast::IdentPat> { - if let ast::Pat::IdentPat(ident) = pat { - Some(ident) - } else { - None - } -} - // Given a match expression, find extracting and diverging arms. fn find_arms( ctx: &AssistContext<'_>, @@ -87,7 +78,7 @@ fn find_arms( let mut extracting = None; let mut diverging = None; for arm in arms { - if ctx.sema.type_of_expr(&arm.expr().unwrap()).unwrap().original().is_never() { + if ctx.sema.type_of_expr(&arm.expr()?)?.original().is_never() { diverging = Some(arm); } else { extracting = Some(arm); @@ -124,7 +115,7 @@ fn find_extracted_variable(ctx: &AssistContext<'_>, arm: &ast::MatchArm) -> Opti } // Rename `extracted` with `binding` in `pat`. -fn rename_variable(pat: &ast::Pat, extracted: ast::Name, binding: ast::IdentPat) -> SyntaxNode { +fn rename_variable(pat: &ast::Pat, extracted: ast::Name, binding: ast::Pat) -> SyntaxNode { let syntax = pat.syntax().clone_for_update(); let extracted_syntax = syntax.covering_element(extracted.syntax().text_range()); @@ -136,7 +127,7 @@ fn rename_variable(pat: &ast::Pat, extracted: ast::Name, binding: ast::IdentPat) if let Some(name_ref) = record_pat_field.field_name() { ted::replace( record_pat_field.syntax(), - ast::make::record_pat_field(ast::make::name_ref(&name_ref.text()), binding.into()) + ast::make::record_pat_field(ast::make::name_ref(&name_ref.text()), binding) .syntax() .clone_for_update(), ); @@ -410,4 +401,52 @@ fn foo(opt: Option<i32>) -> Option<i32> { "#, ); } + + #[test] + fn complex_pattern() { + check_assist( + convert_match_to_let_else, + r#" +//- minicore: option +fn f() { + let (x, y) = $0match Some((0, 1)) { + Some(it) => it, + None => return, + }; +} +"#, + r#" +fn f() { + let Some((x, y)) = Some((0, 1)) else { return }; +} +"#, + ); + } + + #[test] + fn diverging_block() { + check_assist( + convert_match_to_let_else, + r#" +//- minicore: option +fn f() { + let x = $0match Some(()) { + Some(it) => it, + None => {//comment + println!("nope"); + return + }, + }; +} +"#, + r#" +fn f() { + let Some(x) = Some(()) else {//comment + println!("nope"); + return + }; +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs index 8d11e0bac..9dc1da246 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs @@ -52,10 +52,7 @@ pub(crate) fn convert_named_struct_to_tuple_struct( acc: &mut Assists, ctx: &AssistContext<'_>, ) -> Option<()> { - let strukt = ctx - .find_node_at_offset::<ast::Struct>() - .map(Either::Left) - .or_else(|| ctx.find_node_at_offset::<ast::Variant>().map(Either::Right))?; + let strukt = ctx.find_node_at_offset::<Either<ast::Struct, ast::Variant>>()?; let field_list = strukt.as_ref().either(|s| s.field_list(), |v| v.field_list())?; let record_fields = match field_list { ast::FieldList::RecordFieldList(it) => it, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs index b0383291e..772e032fb 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -50,10 +50,7 @@ pub(crate) fn convert_tuple_struct_to_named_struct( acc: &mut Assists, ctx: &AssistContext<'_>, ) -> Option<()> { - let strukt = ctx - .find_node_at_offset::<ast::Struct>() - .map(Either::Left) - .or_else(|| ctx.find_node_at_offset::<ast::Variant>().map(Either::Right))?; + let strukt = ctx.find_node_at_offset::<Either<ast::Struct, ast::Variant>>()?; let field_list = strukt.as_ref().either(|s| s.field_list(), |v| v.field_list())?; let tuple_fields = match field_list { ast::FieldList::TupleFieldList(it) => it, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs new file mode 100644 index 000000000..226a5dd9f --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs @@ -0,0 +1,312 @@ +use either::Either; +use itertools::Itertools; +use syntax::{ + ast::{self, edit::IndentLevel, CommentPlacement, Whitespace}, + AstToken, TextRange, +}; + +use crate::{ + handlers::convert_comment_block::{line_comment_text, relevant_line_comments}, + utils::required_hashes, + AssistContext, AssistId, AssistKind, Assists, +}; + +// Assist: desugar_doc_comment +// +// Desugars doc-comments to the attribute form. +// +// ``` +// /// Multi-line$0 +// /// comment +// ``` +// -> +// ``` +// #[doc = r"Multi-line +// comment"] +// ``` +pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let comment = ctx.find_token_at_offset::<ast::Comment>()?; + // Only allow doc comments + let Some(placement) = comment.kind().doc else { return None; }; + + // Only allow comments which are alone on their line + if let Some(prev) = comment.syntax().prev_token() { + if Whitespace::cast(prev).filter(|w| w.text().contains('\n')).is_none() { + return None; + } + } + + let indentation = IndentLevel::from_token(comment.syntax()).to_string(); + + let (target, comments) = match comment.kind().shape { + ast::CommentShape::Block => (comment.syntax().text_range(), Either::Left(comment)), + ast::CommentShape::Line => { + // Find all the comments we'll be desugaring + let comments = relevant_line_comments(&comment); + + // Establish the target of our edit based on the comments we found + ( + TextRange::new( + comments[0].syntax().text_range().start(), + comments.last().unwrap().syntax().text_range().end(), + ), + Either::Right(comments), + ) + } + }; + + acc.add( + AssistId("desugar_doc_comment", AssistKind::RefactorRewrite), + "Desugar doc-comment to attribute macro", + target, + |edit| { + let text = match comments { + Either::Left(comment) => { + let text = comment.text(); + text[comment.prefix().len()..(text.len() - "*/".len())] + .trim() + .lines() + .map(|l| l.strip_prefix(&indentation).unwrap_or(l)) + .join("\n") + } + Either::Right(comments) => { + comments.into_iter().map(|c| line_comment_text(IndentLevel(0), c)).join("\n") + } + }; + + let hashes = "#".repeat(required_hashes(&text)); + + let prefix = match placement { + CommentPlacement::Inner => "#!", + CommentPlacement::Outer => "#", + }; + + let output = format!(r#"{prefix}[doc = r{hashes}"{text}"{hashes}]"#); + + edit.replace(target, output) + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn single_line() { + check_assist( + desugar_doc_comment, + r#" +/// line$0 comment +fn main() { + foo(); +} +"#, + r#" +#[doc = r"line comment"] +fn main() { + foo(); +} +"#, + ); + check_assist( + desugar_doc_comment, + r#" +//! line$0 comment +fn main() { + foo(); +} +"#, + r#" +#![doc = r"line comment"] +fn main() { + foo(); +} +"#, + ); + } + + #[test] + fn single_line_indented() { + check_assist( + desugar_doc_comment, + r#" +fn main() { + /// line$0 comment + struct Foo; +} +"#, + r#" +fn main() { + #[doc = r"line comment"] + struct Foo; +} +"#, + ); + } + + #[test] + fn multiline() { + check_assist( + desugar_doc_comment, + r#" +fn main() { + /// above + /// line$0 comment + /// + /// below + struct Foo; +} +"#, + r#" +fn main() { + #[doc = r"above +line comment + +below"] + struct Foo; +} +"#, + ); + } + + #[test] + fn end_of_line() { + check_assist_not_applicable( + desugar_doc_comment, + r#" +fn main() { /// end-of-line$0 comment + struct Foo; +} +"#, + ); + } + + #[test] + fn single_line_different_kinds() { + check_assist( + desugar_doc_comment, + r#" +fn main() { + //! different prefix + /// line$0 comment + /// below + struct Foo; +} +"#, + r#" +fn main() { + //! different prefix + #[doc = r"line comment +below"] + struct Foo; +} +"#, + ); + } + + #[test] + fn single_line_separate_chunks() { + check_assist( + desugar_doc_comment, + r#" +/// different chunk + +/// line$0 comment +/// below +"#, + r#" +/// different chunk + +#[doc = r"line comment +below"] +"#, + ); + } + + #[test] + fn block_comment() { + check_assist( + desugar_doc_comment, + r#" +/** + hi$0 there +*/ +"#, + r#" +#[doc = r"hi there"] +"#, + ); + } + + #[test] + fn inner_doc_block() { + check_assist( + desugar_doc_comment, + r#" +/*! + hi$0 there +*/ +"#, + r#" +#![doc = r"hi there"] +"#, + ); + } + + #[test] + fn block_indent() { + check_assist( + desugar_doc_comment, + r#" +fn main() { + /*! + hi$0 there + + ``` + code_sample + ``` + */ +} +"#, + r#" +fn main() { + #![doc = r"hi there + +``` + code_sample +```"] +} +"#, + ); + } + + #[test] + fn end_of_line_block() { + check_assist_not_applicable( + desugar_doc_comment, + r#" +fn main() { + foo(); /** end-of-line$0 comment */ +} +"#, + ); + } + + #[test] + fn regular_comment() { + check_assist_not_applicable(desugar_doc_comment, r#"// some$0 comment"#); + check_assist_not_applicable(desugar_doc_comment, r#"/* some$0 comment*/"#); + } + + #[test] + fn quotes_and_escapes() { + check_assist( + desugar_doc_comment, + r###"/// some$0 "\ "## comment"###, + r####"#[doc = r###"some "\ "## comment"###]"####, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs index 0505f5784..b310c2db9 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs @@ -1,9 +1,6 @@ use either::Either; use ide_db::syntax_helpers::node_ext::walk_ty; -use syntax::{ - ast::{self, edit::IndentLevel, make, AstNode, HasGenericParams, HasName}, - match_ast, -}; +use syntax::ast::{self, edit::IndentLevel, make, AstNode, HasGenericParams, HasName}; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -31,15 +28,8 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> let ty = ctx.find_node_at_range::<ast::Type>()?; let item = ty.syntax().ancestors().find_map(ast::Item::cast)?; - let assoc_owner = item.syntax().ancestors().nth(2).and_then(|it| { - match_ast! { - match it { - ast::Trait(tr) => Some(Either::Left(tr)), - ast::Impl(impl_) => Some(Either::Right(impl_)), - _ => None, - } - } - }); + let assoc_owner = + item.syntax().ancestors().nth(2).and_then(Either::<ast::Trait, ast::Impl>::cast); let node = assoc_owner.as_ref().map_or_else( || item.syntax(), |impl_| impl_.as_ref().either(AstNode::syntax, AstNode::syntax), @@ -161,19 +151,17 @@ fn collect_used_generics<'gp>( .and_then(|lt| known_generics.iter().find(find_lifetime(<.text()))), ), ast::Type::ArrayType(ar) => { - if let Some(expr) = ar.expr() { - if let ast::Expr::PathExpr(p) = expr { - if let Some(path) = p.path() { - if let Some(name_ref) = path.as_single_name_ref() { - if let Some(param) = known_generics.iter().find(|gp| { - if let ast::GenericParam::ConstParam(cp) = gp { - cp.name().map_or(false, |n| n.text() == name_ref.text()) - } else { - false - } - }) { - generics.push(param); + if let Some(ast::Expr::PathExpr(p)) = ar.expr() { + if let Some(path) = p.path() { + if let Some(name_ref) = path.as_single_name_ref() { + if let Some(param) = known_generics.iter().find(|gp| { + if let ast::GenericParam::ConstParam(cp) = gp { + cp.name().map_or(false, |n| n.text() == name_ref.text()) + } else { + false } + }) { + generics.push(param); } } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs index 2d074a33e..860372941 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_new.rs @@ -82,18 +82,18 @@ fn generate_trait_impl_text_from_impl(impl_: &ast::Impl, trait_text: &str, code: let generic_params = impl_.generic_param_list().map(|generic_params| { let lifetime_params = generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam); - let ty_or_const_params = generic_params.type_or_const_params().filter_map(|param| { + let ty_or_const_params = generic_params.type_or_const_params().map(|param| { // remove defaults since they can't be specified in impls match param { ast::TypeOrConstParam::Type(param) => { let param = param.clone_for_update(); param.remove_default(); - Some(ast::GenericParam::TypeParam(param)) + ast::GenericParam::TypeParam(param) } ast::TypeOrConstParam::Const(param) => { let param = param.clone_for_update(); param.remove_default(); - Some(ast::GenericParam::ConstParam(param)) + ast::GenericParam::ConstParam(param) } } }); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs index c8d0493d0..ed1b8f4e2 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_methods.rs @@ -109,7 +109,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' let tail_expr_finished = if is_async { make::expr_await(tail_expr) } else { tail_expr }; let body = make::block_expr([], Some(tail_expr_finished)); - let f = make::fn_(vis, name, type_params, params, body, ret_type, is_async) + let f = make::fn_(vis, name, type_params, None, params, body, ret_type, is_async) .indent(ast::edit::IndentLevel(1)) .clone_for_update(); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs index da9b0cda5..45b27a63c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs @@ -1,8 +1,11 @@ -use hir::{Adt, HasSource, HirDisplay, Module, Semantics, TypeInfo}; +use hir::{ + Adt, AsAssocItem, HasSource, HirDisplay, Module, PathResolution, Semantics, Type, TypeInfo, +}; use ide_db::{ base_db::FileId, defs::{Definition, NameRefClass}, famous_defs::FamousDefs, + path_transform::PathTransform, FxHashMap, FxHashSet, RootDatabase, SnippetCap, }; use stdx::to_lower_snake_case; @@ -10,14 +13,13 @@ use syntax::{ ast::{ self, edit::{AstNodeEdit, IndentLevel}, - make, AstNode, CallExpr, HasArgList, HasModuleItem, + make, AstNode, CallExpr, HasArgList, HasGenericParams, HasModuleItem, HasTypeBounds, }, SyntaxKind, SyntaxNode, TextRange, TextSize, }; use crate::{ - utils::convert_reference_type, - utils::{find_struct_impl, render_snippet, Cursor}, + utils::{convert_reference_type, find_struct_impl, render_snippet, Cursor}, AssistContext, AssistId, AssistKind, Assists, }; @@ -107,7 +109,7 @@ fn fn_target_info( match path.qualifier() { Some(qualifier) => match ctx.sema.resolve_path(&qualifier) { Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) => { - get_fn_target_info(ctx, &Some(module), call.clone()) + get_fn_target_info(ctx, Some(module), call.clone()) } Some(hir::PathResolution::Def(hir::ModuleDef::Adt(adt))) => { if let hir::Adt::Enum(_) = adt { @@ -125,7 +127,7 @@ fn fn_target_info( } _ => None, }, - _ => get_fn_target_info(ctx, &None, call.clone()), + _ => get_fn_target_info(ctx, None, call.clone()), } } @@ -136,7 +138,8 @@ fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { } let fn_name = call.name_ref()?; - let adt = ctx.sema.type_of_expr(&call.receiver()?)?.original().strip_references().as_adt()?; + let receiver_ty = ctx.sema.type_of_expr(&call.receiver()?)?.original().strip_references(); + let adt = receiver_ty.as_adt()?; let current_module = ctx.sema.scope(call.syntax())?.module(); let target_module = adt.module(ctx.sema.db); @@ -147,8 +150,14 @@ fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let (impl_, file) = get_adt_source(ctx, &adt, fn_name.text().as_str())?; let (target, insert_offset) = get_method_target(ctx, &impl_, &adt)?; - let function_builder = - FunctionBuilder::from_method_call(ctx, &call, &fn_name, target_module, target)?; + let function_builder = FunctionBuilder::from_method_call( + ctx, + &call, + &fn_name, + receiver_ty, + target_module, + target, + )?; let text_range = call.syntax().text_range(); let adt_name = if impl_.is_none() { Some(adt.name(ctx.sema.db)) } else { None }; let label = format!("Generate {} method", function_builder.fn_name); @@ -179,6 +188,7 @@ fn add_func_to_accumulator( let function_template = function_builder.render(adt_name.is_some()); let mut func = function_template.to_string(ctx.config.snippet_cap); if let Some(name) = adt_name { + // FIXME: adt may have generic params. func = format!("\n{indent}impl {name} {{\n{func}\n{indent}}}"); } builder.edit_file(file); @@ -238,7 +248,8 @@ impl FunctionTemplate { struct FunctionBuilder { target: GeneratedFunctionTarget, fn_name: ast::Name, - type_params: Option<ast::GenericParamList>, + generic_param_list: Option<ast::GenericParamList>, + where_clause: Option<ast::WhereClause>, params: ast::ParamList, ret_type: Option<ast::RetType>, should_focus_return_type: bool, @@ -260,19 +271,32 @@ impl FunctionBuilder { let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).map(|it| it.module()))?; let fn_name = make::name(fn_name); - let (type_params, params) = - fn_args(ctx, target_module, ast::CallableExpr::Call(call.clone()))?; + let mut necessary_generic_params = FxHashSet::default(); + let params = fn_args( + ctx, + target_module, + ast::CallableExpr::Call(call.clone()), + &mut necessary_generic_params, + )?; let await_expr = call.syntax().parent().and_then(ast::AwaitExpr::cast); let is_async = await_expr.is_some(); - let (ret_type, should_focus_return_type) = - make_return_type(ctx, &ast::Expr::CallExpr(call.clone()), target_module); + let (ret_type, should_focus_return_type) = make_return_type( + ctx, + &ast::Expr::CallExpr(call.clone()), + target_module, + &mut necessary_generic_params, + ); + + let (generic_param_list, where_clause) = + fn_generic_params(ctx, necessary_generic_params, &target)?; Some(Self { target, fn_name, - type_params, + generic_param_list, + where_clause, params, ret_type, should_focus_return_type, @@ -285,25 +309,40 @@ impl FunctionBuilder { ctx: &AssistContext<'_>, call: &ast::MethodCallExpr, name: &ast::NameRef, + receiver_ty: Type, target_module: Module, target: GeneratedFunctionTarget, ) -> Option<Self> { let needs_pub = !module_is_descendant(&ctx.sema.scope(call.syntax())?.module(), &target_module, ctx); let fn_name = make::name(&name.text()); - let (type_params, params) = - fn_args(ctx, target_module, ast::CallableExpr::MethodCall(call.clone()))?; + let mut necessary_generic_params = FxHashSet::default(); + necessary_generic_params.extend(receiver_ty.generic_params(ctx.db())); + let params = fn_args( + ctx, + target_module, + ast::CallableExpr::MethodCall(call.clone()), + &mut necessary_generic_params, + )?; let await_expr = call.syntax().parent().and_then(ast::AwaitExpr::cast); let is_async = await_expr.is_some(); - let (ret_type, should_focus_return_type) = - make_return_type(ctx, &ast::Expr::MethodCallExpr(call.clone()), target_module); + let (ret_type, should_focus_return_type) = make_return_type( + ctx, + &ast::Expr::MethodCallExpr(call.clone()), + target_module, + &mut necessary_generic_params, + ); + + let (generic_param_list, where_clause) = + fn_generic_params(ctx, necessary_generic_params, &target)?; Some(Self { target, fn_name, - type_params, + generic_param_list, + where_clause, params, ret_type, should_focus_return_type, @@ -319,7 +358,8 @@ impl FunctionBuilder { let mut fn_def = make::fn_( visibility, self.fn_name, - self.type_params, + self.generic_param_list, + self.where_clause, self.params, fn_body, self.ret_type, @@ -375,6 +415,7 @@ fn make_return_type( ctx: &AssistContext<'_>, call: &ast::Expr, target_module: Module, + necessary_generic_params: &mut FxHashSet<hir::GenericParam>, ) -> (Option<ast::RetType>, bool) { let (ret_ty, should_focus_return_type) = { match ctx.sema.type_of_expr(call).map(TypeInfo::original) { @@ -382,6 +423,7 @@ fn make_return_type( None => (Some(make::ty_placeholder()), true), Some(ty) if ty.is_unit() => (None, false), Some(ty) => { + necessary_generic_params.extend(ty.generic_params(ctx.db())); let rendered = ty.display_source_code(ctx.db(), target_module.into()); match rendered { Ok(rendered) => (Some(make::ty(&rendered)), false), @@ -396,16 +438,16 @@ fn make_return_type( fn get_fn_target_info( ctx: &AssistContext<'_>, - target_module: &Option<Module>, + target_module: Option<Module>, call: CallExpr, ) -> Option<TargetInfo> { let (target, file, insert_offset) = get_fn_target(ctx, target_module, call)?; - Some(TargetInfo::new(*target_module, None, target, file, insert_offset)) + Some(TargetInfo::new(target_module, None, target, file, insert_offset)) } fn get_fn_target( ctx: &AssistContext<'_>, - target_module: &Option<Module>, + target_module: Option<Module>, call: CallExpr, ) -> Option<(GeneratedFunctionTarget, FileId, TextSize)> { let mut file = ctx.file_id(); @@ -473,37 +515,386 @@ impl GeneratedFunctionTarget { GeneratedFunctionTarget::InEmptyItemList(it) => it, } } + + fn parent(&self) -> SyntaxNode { + match self { + GeneratedFunctionTarget::BehindItem(it) => it.parent().expect("item without parent"), + GeneratedFunctionTarget::InEmptyItemList(it) => it.clone(), + } + } } -/// Computes the type variables and arguments required for the generated function +/// Computes parameter list for the generated function. fn fn_args( ctx: &AssistContext<'_>, target_module: hir::Module, call: ast::CallableExpr, -) -> Option<(Option<ast::GenericParamList>, ast::ParamList)> { + necessary_generic_params: &mut FxHashSet<hir::GenericParam>, +) -> Option<ast::ParamList> { let mut arg_names = Vec::new(); let mut arg_types = Vec::new(); for arg in call.arg_list()?.args() { arg_names.push(fn_arg_name(&ctx.sema, &arg)); - arg_types.push(fn_arg_type(ctx, target_module, &arg)); + arg_types.push(fn_arg_type(ctx, target_module, &arg, necessary_generic_params)); } deduplicate_arg_names(&mut arg_names); let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| { make::param(make::ext::simple_ident_pat(make::name(&name)).into(), make::ty(&ty)) }); - Some(( - None, - make::param_list( - match call { - ast::CallableExpr::Call(_) => None, - ast::CallableExpr::MethodCall(_) => Some(make::self_param()), - }, - params, - ), + Some(make::param_list( + match call { + ast::CallableExpr::Call(_) => None, + ast::CallableExpr::MethodCall(_) => Some(make::self_param()), + }, + params, )) } +/// Gets parameter bounds and where predicates in scope and filters out irrelevant ones. Returns +/// `None` when it fails to get scope information. +/// +/// See comment on `filter_unnecessary_bounds()` for what bounds we consider relevant. +/// +/// NOTE: Generic parameters returned from this function may cause name clash at `target`. We don't +/// currently do anything about it because it's actually easy to resolve it after the assist: just +/// use the Rename functionality. +fn fn_generic_params( + ctx: &AssistContext<'_>, + necessary_params: FxHashSet<hir::GenericParam>, + target: &GeneratedFunctionTarget, +) -> Option<(Option<ast::GenericParamList>, Option<ast::WhereClause>)> { + if necessary_params.is_empty() { + // Not really needed but fast path. + return Some((None, None)); + } + + // 1. Get generic parameters (with bounds) and where predicates in scope. + let (generic_params, where_preds) = params_and_where_preds_in_scope(ctx); + + // 2. Extract type parameters included in each bound. + let mut generic_params = generic_params + .into_iter() + .filter_map(|it| compute_contained_params_in_generic_param(ctx, it)) + .collect(); + let mut where_preds = where_preds + .into_iter() + .filter_map(|it| compute_contained_params_in_where_pred(ctx, it)) + .collect(); + + // 3. Filter out unnecessary bounds. + filter_unnecessary_bounds(&mut generic_params, &mut where_preds, necessary_params); + filter_bounds_in_scope(&mut generic_params, &mut where_preds, ctx, target); + + let generic_params: Vec<_> = + generic_params.into_iter().map(|it| it.node.clone_for_update()).collect(); + let where_preds: Vec<_> = + where_preds.into_iter().map(|it| it.node.clone_for_update()).collect(); + + // 4. Rewrite paths + if let Some(param) = generic_params.first() { + let source_scope = ctx.sema.scope(param.syntax())?; + let target_scope = ctx.sema.scope(&target.parent())?; + if source_scope.module() != target_scope.module() { + let transform = PathTransform::generic_transformation(&target_scope, &source_scope); + let generic_params = generic_params.iter().map(|it| it.syntax()); + let where_preds = where_preds.iter().map(|it| it.syntax()); + transform.apply_all(generic_params.chain(where_preds)); + } + } + + let generic_param_list = make::generic_param_list(generic_params); + let where_clause = + if where_preds.is_empty() { None } else { Some(make::where_clause(where_preds)) }; + + Some((Some(generic_param_list), where_clause)) +} + +fn params_and_where_preds_in_scope( + ctx: &AssistContext<'_>, +) -> (Vec<ast::GenericParam>, Vec<ast::WherePred>) { + let Some(body) = containing_body(ctx) else { return Default::default(); }; + + let mut generic_params = Vec::new(); + let mut where_clauses = Vec::new(); + + // There are two items where generic parameters currently in scope may be declared: the item + // the cursor is at, and its parent (if any). + // + // We handle parent first so that their generic parameters appear first in the generic + // parameter list of the function we're generating. + let db = ctx.db(); + if let Some(parent) = body.as_assoc_item(db).map(|it| it.container(db)) { + match parent { + hir::AssocItemContainer::Impl(it) => { + let (params, clauses) = get_bounds_in_scope(ctx, it); + generic_params.extend(params); + where_clauses.extend(clauses); + } + hir::AssocItemContainer::Trait(it) => { + let (params, clauses) = get_bounds_in_scope(ctx, it); + generic_params.extend(params); + where_clauses.extend(clauses); + } + } + } + + // Other defs with body may inherit generic parameters from its parent, but never have their + // own generic parameters. + if let hir::DefWithBody::Function(it) = body { + let (params, clauses) = get_bounds_in_scope(ctx, it); + generic_params.extend(params); + where_clauses.extend(clauses); + } + + (generic_params, where_clauses) +} + +fn containing_body(ctx: &AssistContext<'_>) -> Option<hir::DefWithBody> { + let item: ast::Item = ctx.find_node_at_offset()?; + let def = match item { + ast::Item::Fn(it) => ctx.sema.to_def(&it)?.into(), + ast::Item::Const(it) => ctx.sema.to_def(&it)?.into(), + ast::Item::Static(it) => ctx.sema.to_def(&it)?.into(), + _ => return None, + }; + Some(def) +} + +fn get_bounds_in_scope<D>( + ctx: &AssistContext<'_>, + def: D, +) -> (impl Iterator<Item = ast::GenericParam>, impl Iterator<Item = ast::WherePred>) +where + D: HasSource, + D::Ast: HasGenericParams, +{ + // This function should be only called with `Impl`, `Trait`, or `Function`, for which it's + // infallible to get source ast. + let node = ctx.sema.source(def).unwrap().value; + let generic_params = node.generic_param_list().into_iter().flat_map(|it| it.generic_params()); + let where_clauses = node.where_clause().into_iter().flat_map(|it| it.predicates()); + (generic_params, where_clauses) +} + +#[derive(Debug)] +struct ParamBoundWithParams { + node: ast::GenericParam, + /// Generic parameter `node` introduces. + /// + /// ```text + /// impl<T> S<T> { + /// fn f<U: Trait<T>>() {} + /// ^ this + /// } + /// ``` + /// + /// `U` in this example. + self_ty_param: hir::GenericParam, + /// Generic parameters contained in the trait reference of this bound. + /// + /// ```text + /// impl<T> S<T> { + /// fn f<U: Trait<T>>() {} + /// ^^^^^^^^ params in this part + /// } + /// ``` + /// + /// `T` in this example. + other_params: FxHashSet<hir::GenericParam>, +} + +#[derive(Debug)] +struct WherePredWithParams { + node: ast::WherePred, + /// Generic parameters contained in the "self type" of this where predicate. + /// + /// ```text + /// Struct<T, U>: Trait<T, Assoc = V>, + /// ^^^^^^^^^^^^ params in this part + /// ``` + /// + /// `T` and `U` in this example. + self_ty_params: FxHashSet<hir::GenericParam>, + /// Generic parameters contained in the trait reference of this where predicate. + /// + /// ```text + /// Struct<T, U>: Trait<T, Assoc = V>, + /// ^^^^^^^^^^^^^^^^^^^ params in this part + /// ``` + /// + /// `T` and `V` in this example. + other_params: FxHashSet<hir::GenericParam>, +} + +fn compute_contained_params_in_generic_param( + ctx: &AssistContext<'_>, + node: ast::GenericParam, +) -> Option<ParamBoundWithParams> { + match &node { + ast::GenericParam::TypeParam(ty) => { + let self_ty_param = ctx.sema.to_def(ty)?.into(); + + let other_params = ty + .type_bound_list() + .into_iter() + .flat_map(|it| it.bounds()) + .flat_map(|bound| bound.syntax().descendants()) + .filter_map(|node| filter_generic_params(ctx, node)) + .collect(); + + Some(ParamBoundWithParams { node, self_ty_param, other_params }) + } + ast::GenericParam::ConstParam(ct) => { + let self_ty_param = ctx.sema.to_def(ct)?.into(); + Some(ParamBoundWithParams { node, self_ty_param, other_params: FxHashSet::default() }) + } + ast::GenericParam::LifetimeParam(_) => { + // FIXME: It might be a good idea to handle lifetime parameters too. + None + } + } +} + +fn compute_contained_params_in_where_pred( + ctx: &AssistContext<'_>, + node: ast::WherePred, +) -> Option<WherePredWithParams> { + let self_ty = node.ty()?; + let bound_list = node.type_bound_list()?; + + let self_ty_params = self_ty + .syntax() + .descendants() + .filter_map(|node| filter_generic_params(ctx, node)) + .collect(); + + let other_params = bound_list + .bounds() + .flat_map(|bound| bound.syntax().descendants()) + .filter_map(|node| filter_generic_params(ctx, node)) + .collect(); + + Some(WherePredWithParams { node, self_ty_params, other_params }) +} + +fn filter_generic_params(ctx: &AssistContext<'_>, node: SyntaxNode) -> Option<hir::GenericParam> { + let path = ast::Path::cast(node)?; + match ctx.sema.resolve_path(&path)? { + PathResolution::TypeParam(it) => Some(it.into()), + PathResolution::ConstParam(it) => Some(it.into()), + _ => None, + } +} + +/// Filters out irrelevant bounds from `generic_params` and `where_preds`. +/// +/// Say we have a trait bound `Struct<T>: Trait<U>`. Given `necessary_params`, when is it relevant +/// and when not? Some observations: +/// - When `necessary_params` contains `T`, it's likely that we want this bound, but now we have +/// an extra param to consider: `U`. +/// - On the other hand, when `necessary_params` contains `U` (but not `T`), then it's unlikely +/// that we want this bound because it doesn't really constrain `U`. +/// +/// (FIXME?: The latter clause might be overstating. We may want to include the bound if the self +/// type does *not* include generic params at all - like `Option<i32>: From<U>`) +/// +/// Can we make this a bit more formal? Let's define "dependency" between generic parameters and +/// trait bounds: +/// - A generic parameter `T` depends on a trait bound if `T` appears in the self type (i.e. left +/// part) of the bound. +/// - A trait bound depends on a generic parameter `T` if `T` appears in the bound. +/// +/// Using the notion, what we want is all the bounds that params in `necessary_params` +/// *transitively* depend on! +/// +/// Now it's not hard to solve: we build a dependency graph and compute all reachable nodes from +/// nodes that represent params in `necessary_params` by usual and boring DFS. +/// +/// The time complexity is O(|generic_params| + |where_preds| + |necessary_params|). +fn filter_unnecessary_bounds( + generic_params: &mut Vec<ParamBoundWithParams>, + where_preds: &mut Vec<WherePredWithParams>, + necessary_params: FxHashSet<hir::GenericParam>, +) { + // All `self_ty_param` should be unique as they were collected from `ast::GenericParamList`s. + let param_map: FxHashMap<hir::GenericParam, usize> = + generic_params.iter().map(|it| it.self_ty_param).zip(0..).collect(); + let param_count = param_map.len(); + let generic_params_upper_bound = param_count + generic_params.len(); + let node_count = generic_params_upper_bound + where_preds.len(); + + // | node index range | what the node represents | + // |-----------------------------------------|--------------------------| + // | 0..param_count | generic parameter | + // | param_count..generic_params_upper_bound | `ast::GenericParam` | + // | generic_params_upper_bound..node_count | `ast::WherePred` | + let mut graph = Graph::new(node_count); + for (pred, pred_idx) in generic_params.iter().zip(param_count..) { + let param_idx = param_map[&pred.self_ty_param]; + graph.add_edge(param_idx, pred_idx); + graph.add_edge(pred_idx, param_idx); + + for param in &pred.other_params { + let param_idx = param_map[param]; + graph.add_edge(pred_idx, param_idx); + } + } + for (pred, pred_idx) in where_preds.iter().zip(generic_params_upper_bound..) { + for param in &pred.self_ty_params { + let param_idx = param_map[param]; + graph.add_edge(param_idx, pred_idx); + graph.add_edge(pred_idx, param_idx); + } + for param in &pred.other_params { + let param_idx = param_map[param]; + graph.add_edge(pred_idx, param_idx); + } + } + + let starting_nodes = necessary_params.iter().map(|param| param_map[param]); + let reachable = graph.compute_reachable_nodes(starting_nodes); + + // Not pretty, but effective. If only there were `Vec::retain_index()`... + let mut idx = param_count; + generic_params.retain(|_| { + idx += 1; + reachable[idx - 1] + }); + stdx::always!(idx == generic_params_upper_bound, "inconsistent index"); + where_preds.retain(|_| { + idx += 1; + reachable[idx - 1] + }); +} + +/// Filters out bounds from impl if we're generating the function into the same impl we're +/// generating from. +fn filter_bounds_in_scope( + generic_params: &mut Vec<ParamBoundWithParams>, + where_preds: &mut Vec<WherePredWithParams>, + ctx: &AssistContext<'_>, + target: &GeneratedFunctionTarget, +) -> Option<()> { + let target_impl = target.parent().ancestors().find_map(ast::Impl::cast)?; + let target_impl = ctx.sema.to_def(&target_impl)?; + // It's sufficient to test only the first element of `generic_params` because of the order of + // insertion (see `relevant_parmas_and_where_clauses()`). + let def = generic_params.first()?.self_ty_param.parent(); + if def != hir::GenericDef::Impl(target_impl) { + return None; + } + + // Now we know every element that belongs to an impl would be in scope at `target`, we can + // filter them out just by lookint at their parent. + generic_params.retain(|it| !matches!(it.self_ty_param.parent(), hir::GenericDef::Impl(_))); + where_preds.retain(|it| { + it.node.syntax().parent().and_then(|it| it.parent()).and_then(ast::Impl::cast).is_none() + }); + + Some(()) +} + /// Makes duplicate argument names unique by appending incrementing numbers. /// /// ``` @@ -564,17 +955,25 @@ fn fn_arg_name(sema: &Semantics<'_, RootDatabase>, arg_expr: &ast::Expr) -> Stri } } -fn fn_arg_type(ctx: &AssistContext<'_>, target_module: hir::Module, fn_arg: &ast::Expr) -> String { +fn fn_arg_type( + ctx: &AssistContext<'_>, + target_module: hir::Module, + fn_arg: &ast::Expr, + generic_params: &mut FxHashSet<hir::GenericParam>, +) -> String { fn maybe_displayed_type( ctx: &AssistContext<'_>, target_module: hir::Module, fn_arg: &ast::Expr, + generic_params: &mut FxHashSet<hir::GenericParam>, ) -> Option<String> { let ty = ctx.sema.type_of_expr(fn_arg)?.adjusted(); if ty.is_unknown() { return None; } + generic_params.extend(ty.generic_params(ctx.db())); + if ty.is_reference() || ty.is_mutable_reference() { let famous_defs = &FamousDefs(&ctx.sema, ctx.sema.scope(fn_arg.syntax())?.krate()); convert_reference_type(ty.strip_references(), ctx.db(), famous_defs) @@ -585,7 +984,8 @@ fn fn_arg_type(ctx: &AssistContext<'_>, target_module: hir::Module, fn_arg: &ast } } - maybe_displayed_type(ctx, target_module, fn_arg).unwrap_or_else(|| String::from("_")) + maybe_displayed_type(ctx, target_module, fn_arg, generic_params) + .unwrap_or_else(|| String::from("_")) } /// Returns the position inside the current mod or file @@ -640,10 +1040,11 @@ fn next_space_for_fn_in_module( } fn next_space_for_fn_in_impl(impl_: &ast::Impl) -> Option<GeneratedFunctionTarget> { - if let Some(last_item) = impl_.assoc_item_list().and_then(|it| it.assoc_items().last()) { + let assoc_item_list = impl_.assoc_item_list()?; + if let Some(last_item) = assoc_item_list.assoc_items().last() { Some(GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())) } else { - Some(GeneratedFunctionTarget::InEmptyItemList(impl_.assoc_item_list()?.syntax().clone())) + Some(GeneratedFunctionTarget::InEmptyItemList(assoc_item_list.syntax().clone())) } } @@ -659,6 +1060,73 @@ fn module_is_descendant(module: &hir::Module, ans: &hir::Module, ctx: &AssistCon false } +// This is never intended to be used as a generic graph strucuture. If there's ever another need of +// graph algorithm, consider adding a library for that (and replace the following). +/// Minimally implemented directed graph structure represented by adjacency list. +struct Graph { + edges: Vec<Vec<usize>>, +} + +impl Graph { + fn new(node_count: usize) -> Self { + Self { edges: vec![Vec::new(); node_count] } + } + + fn add_edge(&mut self, from: usize, to: usize) { + self.edges[from].push(to); + } + + fn edges_for(&self, node_idx: usize) -> &[usize] { + &self.edges[node_idx] + } + + fn len(&self) -> usize { + self.edges.len() + } + + fn compute_reachable_nodes( + &self, + starting_nodes: impl IntoIterator<Item = usize>, + ) -> Vec<bool> { + let mut visitor = Visitor::new(self); + for idx in starting_nodes { + visitor.mark_reachable(idx); + } + visitor.visited + } +} + +struct Visitor<'g> { + graph: &'g Graph, + visited: Vec<bool>, + // Stack is held in this struct so we can reuse its buffer. + stack: Vec<usize>, +} + +impl<'g> Visitor<'g> { + fn new(graph: &'g Graph) -> Self { + let visited = vec![false; graph.len()]; + Self { graph, visited, stack: Vec::new() } + } + + fn mark_reachable(&mut self, start_idx: usize) { + // non-recursive DFS + stdx::always!(self.stack.is_empty()); + + self.stack.push(start_idx); + while let Some(idx) = self.stack.pop() { + if !self.visited[idx] { + self.visited[idx] = true; + for &neighbor in self.graph.edges_for(idx) { + if !self.visited[neighbor] { + self.stack.push(neighbor); + } + } + } + } + } +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; @@ -1087,21 +1555,167 @@ fn bar(baz: Baz::Bof) { } #[test] - fn add_function_with_generic_arg() { - // FIXME: This is wrong, generated `bar` should include generic parameter. + fn generate_function_with_generic_param() { + check_assist( + generate_function, + r" +fn foo<T, const N: usize>(t: [T; N]) { $0bar(t) } +", + r" +fn foo<T, const N: usize>(t: [T; N]) { bar(t) } + +fn bar<T, const N: usize>(t: [T; N]) { + ${0:todo!()} +} +", + ) + } + + #[test] + fn generate_function_with_parent_generic_param() { + check_assist( + generate_function, + r" +struct S<T>(T); +impl<T> S<T> { + fn foo<U>(t: T, u: U) { $0bar(t, u) } +} +", + r" +struct S<T>(T); +impl<T> S<T> { + fn foo<U>(t: T, u: U) { bar(t, u) } +} + +fn bar<T, U>(t: T, u: U) { + ${0:todo!()} +} +", + ) + } + + #[test] + fn generic_param_in_receiver_type() { + // FIXME: Generic parameter `T` should be part of impl, not method. + check_assist( + generate_function, + r" +struct S<T>(T); +fn foo<T, U>(s: S<T>, u: U) { s.$0foo(u) } +", + r" +struct S<T>(T); +impl S { + fn foo<T, U>(&self, u: U) { + ${0:todo!()} + } +} +fn foo<T, U>(s: S<T>, u: U) { s.foo(u) } +", + ) + } + + #[test] + fn generic_param_in_return_type() { + check_assist( + generate_function, + r" +fn foo<T, const N: usize>() -> [T; N] { $0bar() } +", + r" +fn foo<T, const N: usize>() -> [T; N] { bar() } + +fn bar<T, const N: usize>() -> [T; N] { + ${0:todo!()} +} +", + ) + } + + #[test] + fn generate_fn_with_bounds() { + // FIXME: where predicates should be on next lines. + check_assist( + generate_function, + r" +trait A<T> {} +struct S<T>(T); +impl<T: A<i32>> S<T> +where + T: A<i64>, +{ + fn foo<U>(t: T, u: U) + where + T: A<()>, + U: A<i32> + A<i64>, + { + $0bar(t, u) + } +} +", + r" +trait A<T> {} +struct S<T>(T); +impl<T: A<i32>> S<T> +where + T: A<i64>, +{ + fn foo<U>(t: T, u: U) + where + T: A<()>, + U: A<i32> + A<i64>, + { + bar(t, u) + } +} + +fn bar<T: A<i32>, U>(t: T, u: U) where T: A<i64>, T: A<()>, U: A<i32> + A<i64> { + ${0:todo!()} +} +", + ) + } + + #[test] + fn include_transitive_param_dependency() { + // FIXME: where predicates should be on next lines. check_assist( generate_function, r" -fn foo<T>(t: T) { - $0bar(t) +trait A<T> { type Assoc; } +trait B { type Item; } +struct S<T>(T); +impl<T, U, V: B, W> S<(T, U, V, W)> +where + T: A<U, Assoc = V>, + S<V::Item>: A<U, Assoc = W>, +{ + fn foo<I>(t: T, u: U) + where + U: A<T, Assoc = I>, + { + $0bar(u) + } } ", r" -fn foo<T>(t: T) { - bar(t) +trait A<T> { type Assoc; } +trait B { type Item; } +struct S<T>(T); +impl<T, U, V: B, W> S<(T, U, V, W)> +where + T: A<U, Assoc = V>, + S<V::Item>: A<U, Assoc = W>, +{ + fn foo<I>(t: T, u: U) + where + U: A<T, Assoc = I>, + { + bar(u) + } } -fn bar(t: T) { +fn bar<T, U, V: B, W, I>(u: U) where T: A<U, Assoc = V>, S<V::Item>: A<U, Assoc = W>, U: A<T, Assoc = I> { ${0:todo!()} } ", @@ -1109,6 +1723,135 @@ fn bar(t: T) { } #[test] + fn irrelevant_bounds_are_filtered_out() { + check_assist( + generate_function, + r" +trait A<T> {} +struct S<T>(T); +impl<T, U, V, W> S<(T, U, V, W)> +where + T: A<U>, + V: A<W>, +{ + fn foo<I>(t: T, u: U) + where + U: A<T> + A<I>, + { + $0bar(u) + } +} +", + r" +trait A<T> {} +struct S<T>(T); +impl<T, U, V, W> S<(T, U, V, W)> +where + T: A<U>, + V: A<W>, +{ + fn foo<I>(t: T, u: U) + where + U: A<T> + A<I>, + { + bar(u) + } +} + +fn bar<T, U, I>(u: U) where T: A<U>, U: A<T> + A<I> { + ${0:todo!()} +} +", + ) + } + + #[test] + fn params_in_trait_arg_are_not_dependency() { + // Even though `bar` depends on `U` and `I`, we don't have to copy these bounds: + // `T: A<I>` and `T: A<U>`. + check_assist( + generate_function, + r" +trait A<T> {} +struct S<T>(T); +impl<T, U> S<(T, U)> +where + T: A<U>, +{ + fn foo<I>(t: T, u: U) + where + T: A<I>, + U: A<I>, + { + $0bar(u) + } +} +", + r" +trait A<T> {} +struct S<T>(T); +impl<T, U> S<(T, U)> +where + T: A<U>, +{ + fn foo<I>(t: T, u: U) + where + T: A<I>, + U: A<I>, + { + bar(u) + } +} + +fn bar<U, I>(u: U) where U: A<I> { + ${0:todo!()} +} +", + ) + } + + #[test] + fn dont_copy_bounds_already_in_scope() { + check_assist( + generate_function, + r" +trait A<T> {} +struct S<T>(T); +impl<T: A<i32>> S<T> +where + T: A<usize>, +{ + fn foo<U: A<()>>(t: T, u: U) + where + T: A<S<i32>>, + { + Self::$0bar(t, u); + } +} +", + r" +trait A<T> {} +struct S<T>(T); +impl<T: A<i32>> S<T> +where + T: A<usize>, +{ + fn foo<U: A<()>>(t: T, u: U) + where + T: A<S<i32>>, + { + Self::bar(t, u); + } + + fn bar<U: A<()>>(t: T, u: U) ${0:-> _} where T: A<S<i32>> { + todo!() + } +} +", + ) + } + + #[test] fn add_function_with_fn_arg() { // FIXME: The argument in `bar` is wrong. check_assist( @@ -1290,6 +2033,50 @@ fn baz(foo: foo::Foo) { } #[test] + fn qualified_path_in_generic_bounds_uses_correct_scope() { + check_assist( + generate_function, + r" +mod a { + pub trait A {}; +} +pub mod b { + pub struct S<T>(T); +} +struct S<T>(T); +impl<T> S<T> +where + T: a::A, +{ + fn foo<U: a::A>(t: b::S<T>, u: S<U>) { + a::$0bar(t, u); + } +} +", + r" +mod a { + pub trait A {} + + pub(crate) fn bar<T, U: self::A>(t: crate::b::S<T>, u: crate::S<U>) ${0:-> _} where T: self::A { + todo!() + }; +} +pub mod b { + pub struct S<T>(T); +} +struct S<T>(T); +impl<T> S<T> +where + T: a::A, +{ + fn foo<U: a::A>(t: b::S<T>, u: S<U>) { + a::bar(t, u); + } +} +", + ) + } + #[test] fn add_function_in_module_containing_other_items() { check_assist( generate_function, @@ -1607,6 +2394,26 @@ fn foo() {S::bar();} } #[test] + fn create_generic_static_method() { + check_assist( + generate_function, + r" +struct S; +fn foo<T, const N: usize>(t: [T; N]) { S::bar$0(t); } +", + r" +struct S; +impl S { + fn bar<T, const N: usize>(t: [T; N]) ${0:-> _} { + todo!() + } +} +fn foo<T, const N: usize>(t: [T; N]) { S::bar(t); } +", + ) + } + + #[test] fn create_static_method_within_an_impl() { check_assist( generate_function, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs index 15641b448..4595cfe29 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter.rs @@ -180,7 +180,9 @@ pub(crate) fn generate_getter_impl( // Insert `$0` only for last getter we generate if i == record_fields_count - 1 { - getter_buf = getter_buf.replacen("fn ", "fn $0", 1); + if ctx.config.snippet_cap.is_some() { + getter_buf = getter_buf.replacen("fn ", "fn $0", 1); + } } // For first element we do not merge with '\n', as @@ -330,7 +332,7 @@ fn parse_record_field(record_field: ast::RecordField, mutable: bool) -> Option<R #[cfg(test)] mod tests { - use crate::tests::{check_assist, check_assist_not_applicable}; + use crate::tests::{check_assist, check_assist_no_snippet_cap, check_assist_not_applicable}; use super::*; @@ -378,6 +380,49 @@ impl Context { } #[test] + fn test_generate_getter_from_field_no_snippet_cap() { + check_assist_no_snippet_cap( + generate_getter, + r#" +struct Context { + dat$0a: Data, +} +"#, + r#" +struct Context { + data: Data, +} + +impl Context { + fn data(&self) -> &Data { + &self.data + } +} +"#, + ); + + check_assist_no_snippet_cap( + generate_getter_mut, + r#" +struct Context { + dat$0a: Data, +} +"#, + r#" +struct Context { + data: Data, +} + +impl Context { + fn data_mut(&mut self) -> &mut Data { + &mut self.data + } +} +"#, + ); + } + + #[test] fn test_generate_getter_already_implemented() { check_assist_not_applicable( generate_getter, @@ -434,6 +479,29 @@ impl Context { } #[test] + fn test_generate_getter_from_field_with_visibility_marker_no_snippet_cap() { + check_assist_no_snippet_cap( + generate_getter, + r#" +pub(crate) struct Context { + dat$0a: Data, +} +"#, + r#" +pub(crate) struct Context { + data: Data, +} + +impl Context { + pub(crate) fn data(&self) -> &Data { + &self.data + } +} +"#, + ); + } + + #[test] fn test_multiple_generate_getter() { check_assist( generate_getter, @@ -469,6 +537,41 @@ impl Context { } #[test] + fn test_multiple_generate_getter_no_snippet_cap() { + check_assist_no_snippet_cap( + generate_getter, + r#" +struct Context { + data: Data, + cou$0nt: usize, +} + +impl Context { + fn data(&self) -> &Data { + &self.data + } +} +"#, + r#" +struct Context { + data: Data, + count: usize, +} + +impl Context { + fn data(&self) -> &Data { + &self.data + } + + fn count(&self) -> &usize { + &self.count + } +} +"#, + ); + } + + #[test] fn test_not_a_special_case() { cov_mark::check_count!(convert_reference_type, 0); // Fake string which doesn't implement AsRef<str> diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs index 9d03f03d2..3fc552306 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs @@ -1,3 +1,4 @@ +use ide_db::syntax_helpers::insert_whitespace_into_node::insert_ws_into; use syntax::ast::{self, AstNode}; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -35,7 +36,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; // ``` pub(crate) fn inline_macro(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let unexpanded = ctx.find_node_at_offset::<ast::MacroCall>()?; - let expanded = ctx.sema.expand(&unexpanded)?.clone_for_update(); + let expanded = insert_ws_into(ctx.sema.expand(&unexpanded)?.clone_for_update()); let text_range = unexpanded.syntax().text_range(); @@ -230,4 +231,27 @@ fn f() { let result = foo$0(); } "#, ); } + + #[test] + fn inline_macro_with_whitespace() { + check_assist( + inline_macro, + r#" +macro_rules! whitespace { + () => { + if true {} + }; +} +fn f() { whitespace$0!(); } +"#, + r#" +macro_rules! whitespace { + () => { + if true {} + }; +} +fn f() { if true{}; } +"#, + ) + } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs index 2bdbec93b..d7ddc5f23 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs @@ -92,7 +92,7 @@ trait Merge: AstNode + Clone { fn try_merge_from(self, items: &mut dyn Iterator<Item = Self>) -> Option<Vec<Edit>> { let mut edits = Vec::new(); let mut merged = self.clone(); - while let Some(item) = items.next() { + for item in items { merged = merged.try_merge(&item)?; edits.push(Edit::Remove(item.into_either())); } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs index 0e3a1e652..d848fce4b 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_const_to_impl.rs @@ -5,10 +5,7 @@ use syntax::{ SyntaxKind, }; -use crate::{ - assist_context::{AssistContext, Assists}, - utils, -}; +use crate::assist_context::{AssistContext, Assists}; // NOTE: Code may break if the self type implements a trait that has associated const with the same // name, but it's pretty expensive to check that (`hir::Impl::all_for_type()`) and we assume that's @@ -130,9 +127,7 @@ pub(crate) fn move_const_to_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> let const_ = const_.clone_for_update(); const_.reindent_to(indent); - let mut const_text = format!("\n{indent}{const_}{fixup}"); - utils::escape_non_snippet(&mut const_text); - builder.insert(insert_offset, const_text); + builder.insert(insert_offset, format!("\n{indent}{const_}{fixup}")); }, ) } @@ -443,39 +438,4 @@ impl S { "#, ); } - - #[test] - fn moved_const_body_is_escaped() { - // Note that the last argument is what *lsp clients would see* rather than - // what users would see. Unescaping happens thereafter. - check_assist( - move_const_to_impl, - r#" -struct S; -impl S { - fn f() -> usize { - /// doc comment - /// \\ - /// ${snippet} - const C$0: &str = "\ and $1"; - - C.len() - } -} -"#, - r#" -struct S; -impl S { - /// doc comment - /// \\\\ - /// \${snippet} - const C: &str = "\\ and \$1"; - - fn f() -> usize { - Self::C.len() - } -} -"#, - ) - } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs index c9bc25b27..01420430b 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use syntax::{ast, ast::IsString, AstToken, TextRange, TextSize}; -use crate::{AssistContext, AssistId, AssistKind, Assists}; +use crate::{utils::required_hashes, AssistContext, AssistId, AssistKind, Assists}; // Assist: make_raw_string // @@ -155,16 +155,6 @@ pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< }) } -fn required_hashes(s: &str) -> usize { - let mut res = 0usize; - for idx in s.match_indices('"').map(|(i, _)| i) { - let (_, sub) = s.split_at(idx + 1); - let n_hashes = sub.chars().take_while(|c| *c == '#').count(); - res = res.max(n_hashes + 1) - } - res -} - #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; @@ -172,17 +162,6 @@ mod tests { use super::*; #[test] - fn test_required_hashes() { - assert_eq!(0, required_hashes("abc")); - assert_eq!(0, required_hashes("###")); - assert_eq!(1, required_hashes("\"")); - assert_eq!(2, required_hashes("\"#abc")); - assert_eq!(0, required_hashes("#abc")); - assert_eq!(3, required_hashes("#ab\"##c")); - assert_eq!(5, required_hashes("#ab\"##\"####c")); - } - - #[test] fn make_raw_string_target() { check_assist_target( make_raw_string, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/reorder_fields.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/reorder_fields.rs index a899c7a64..58dcaf9a2 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/reorder_fields.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/reorder_fields.rs @@ -20,10 +20,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; // const test: Foo = Foo {foo: 1, bar: 0} // ``` pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let record = ctx - .find_node_at_offset::<ast::RecordExpr>() - .map(Either::Left) - .or_else(|| ctx.find_node_at_offset::<ast::RecordPat>().map(Either::Right))?; + let record = ctx.find_node_at_offset::<Either<ast::RecordExpr, ast::RecordPat>>()?; let path = record.as_ref().either(|it| it.path(), |it| it.path())?; let ranks = compute_fields_ranks(&path, ctx)?; diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_arith_op.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_arith_op.rs index f1ca35caf..4b20b35c4 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_arith_op.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_arith_op.rs @@ -81,7 +81,7 @@ fn replace_arith(acc: &mut Assists, ctx: &AssistContext<'_>, kind: ArithKind) -> let range = TextRange::new(start, end); acc.add_group( - &GroupLabel("replace_arith".into()), + &GroupLabel("Replace arithmetic...".into()), kind.assist_id(), kind.label(), range, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs index a6693d7d7..4cfae0c72 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs @@ -214,7 +214,7 @@ fn impl_def_from_trait( // Generate a default `impl` function body for the derived trait. if let ast::AssocItem::Fn(ref func) = first_assoc_item { - let _ = gen_trait_fn_body(func, trait_path, adt); + let _ = gen_trait_fn_body(func, trait_path, adt, None); }; Some((impl_def, first_assoc_item)) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs index 484c27387..457559656 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs @@ -13,7 +13,7 @@ use syntax::{ edit::{AstNodeEdit, IndentLevel}, make, HasName, }, - AstNode, TextRange, + AstNode, TextRange, T, }; use crate::{ @@ -96,8 +96,9 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext<' cond_bodies.push((cond, body)); } - if !pat_seen { - // Don't offer turning an if (chain) without patterns into a match + if !pat_seen && cond_bodies.len() != 1 { + // Don't offer turning an if (chain) without patterns into a match, + // unless its a simple `if cond { .. } (else { .. })` return None; } @@ -114,6 +115,11 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext<' Either::Left(pat) => { make::match_arm(iter::once(pat), None, unwrap_trivial_block(body)) } + Either::Right(_) if !pat_seen => make::match_arm( + iter::once(make::literal_pat("true").into()), + None, + unwrap_trivial_block(body), + ), Either::Right(expr) => make::match_arm( iter::once(make::wildcard_pat().into()), Some(expr), @@ -144,31 +150,36 @@ fn make_else_arm( else_block: Option<ast::BlockExpr>, conditionals: &[(Either<ast::Pat, ast::Expr>, ast::BlockExpr)], ) -> ast::MatchArm { - if let Some(else_block) = else_block { - let pattern = if let [(Either::Left(pat), _)] = conditionals { - ctx.sema + let (pattern, expr) = if let Some(else_block) = else_block { + let pattern = match conditionals { + [(Either::Right(_), _)] => make::literal_pat("false").into(), + [(Either::Left(pat), _)] => match ctx + .sema .type_of_pat(pat) .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty.adjusted())) - .zip(Some(pat)) - } else { - None - }; - let pattern = match pattern { - Some((it, pat)) => { - if does_pat_match_variant(pat, &it.sad_pattern()) { - it.happy_pattern_wildcard() - } else if does_nested_pattern(pat) { - make::wildcard_pat().into() - } else { - it.sad_pattern() + { + Some(it) => { + if does_pat_match_variant(pat, &it.sad_pattern()) { + it.happy_pattern_wildcard() + } else if does_nested_pattern(pat) { + make::wildcard_pat().into() + } else { + it.sad_pattern() + } } - } - None => make::wildcard_pat().into(), + None => make::wildcard_pat().into(), + }, + _ => make::wildcard_pat().into(), }; - make::match_arm(iter::once(pattern), None, unwrap_trivial_block(else_block)) + (pattern, unwrap_trivial_block(else_block)) } else { - make::match_arm(iter::once(make::wildcard_pat().into()), None, make::expr_unit()) - } + let pattern = match conditionals { + [(Either::Right(_), _)] => make::literal_pat("false").into(), + _ => make::wildcard_pat().into(), + }; + (pattern, make::expr_unit()) + }; + make::match_arm(iter::once(pattern), None, expr) } // Assist: replace_match_with_if_let @@ -231,7 +242,19 @@ pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext<' } } - let condition = make::expr_let(if_let_pat, scrutinee); + let condition = match if_let_pat { + ast::Pat::LiteralPat(p) + if p.literal().map_or(false, |it| it.token().kind() == T![true]) => + { + scrutinee + } + ast::Pat::LiteralPat(p) + if p.literal().map_or(false, |it| it.token().kind() == T![false]) => + { + make::expr_prefix(T![!], scrutinee) + } + _ => make::expr_let(if_let_pat, scrutinee).into(), + }; let then_block = make_block_expr(then_expr.reset_indent()); let else_expr = if is_empty_expr(&else_expr) { None } else { Some(else_expr) }; let if_let_expr = make::expr_if( @@ -328,6 +351,58 @@ fn main() { } #[test] + fn test_if_with_match_no_else() { + check_assist( + replace_if_let_with_match, + r#" +pub fn foo(foo: bool) { + if foo$0 { + self.foo(); + } +} +"#, + r#" +pub fn foo(foo: bool) { + match foo { + true => { + self.foo(); + } + false => (), + } +} +"#, + ) + } + + #[test] + fn test_if_with_match_with_else() { + check_assist( + replace_if_let_with_match, + r#" +pub fn foo(foo: bool) { + if foo$0 { + self.foo(); + } else { + self.bar(); + } +} +"#, + r#" +pub fn foo(foo: bool) { + match foo { + true => { + self.foo(); + } + false => { + self.bar(); + } + } +} +"#, + ) + } + + #[test] fn test_if_let_with_match_no_else() { check_assist( replace_if_let_with_match, @@ -996,4 +1071,64 @@ fn main() { "#, ) } + + #[test] + fn test_replace_match_with_if_bool() { + check_assist( + replace_match_with_if_let, + r#" +fn main() { + match$0 b { + true => (), + _ => code(), + } +} +"#, + r#" +fn main() { + if b { + () + } else { + code() + } +} +"#, + ); + check_assist( + replace_match_with_if_let, + r#" +fn main() { + match$0 b { + false => code(), + true => (), + } +} +"#, + r#" +fn main() { + if !b { + code() + } +} +"#, + ); + check_assist( + replace_match_with_if_let, + r#" +fn main() { + match$0 b { + false => (), + true => code(), + } +} +"#, + r#" +fn main() { + if b { + code() + } +} +"#, + ) + } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_match_arm.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_match_arm.rs index 9565f0ee6..db789cfa3 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_match_arm.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_match_arm.rs @@ -86,8 +86,7 @@ pub(crate) fn unmerge_match_arm(acc: &mut Assists, ctx: &AssistContext<'_>) -> O it.prev_sibling_or_token() }) .map(|it| it.kind()) - .skip_while(|it| it.is_trivia()) - .next() + .find(|it| !it.is_trivia()) == Some(T![,]); let has_arms_after = neighbor(&match_arm, Direction::Next).is_some(); if !has_comma_after && !has_arms_after { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs index 53cdac03a..33b19a354 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs @@ -2,6 +2,7 @@ use syntax::{ ast::{ self, edit::{AstNodeEdit, IndentLevel}, + make, }, AstNode, SyntaxKind, TextRange, T, }; @@ -37,61 +38,89 @@ pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option parent = parent.ancestors().find(|it| ast::MatchExpr::can_cast(it.kind()))? } - if matches!(parent.kind(), SyntaxKind::STMT_LIST | SyntaxKind::EXPR_STMT | SyntaxKind::LET_STMT) - { - return acc.add(assist_id, assist_label, target, |builder| { + let kind = parent.kind(); + if matches!(kind, SyntaxKind::STMT_LIST | SyntaxKind::EXPR_STMT) { + acc.add(assist_id, assist_label, target, |builder| { builder.replace(block.syntax().text_range(), update_expr_string(block.to_string())); - }); - } - - let parent = ast::Expr::cast(parent)?; - - match parent.clone() { - ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::LoopExpr(_) => (), - ast::Expr::MatchExpr(_) => block = block.dedent(IndentLevel(1)), - ast::Expr::IfExpr(if_expr) => { - let then_branch = if_expr.then_branch()?; - if then_branch == block { - if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) { - // For `else if` blocks - let ancestor_then_branch = ancestor.then_branch()?; - + }) + } else if matches!(kind, SyntaxKind::LET_STMT) { + let parent = ast::LetStmt::cast(parent)?; + let pattern = ast::Pat::cast(parent.syntax().first_child()?)?; + let ty = parent.ty(); + let list = block.stmt_list()?; + let replaced = match list.syntax().last_child() { + Some(last) => { + let stmts: Vec<ast::Stmt> = list.statements().collect(); + let initializer = ast::Expr::cast(last.clone())?; + let let_stmt = make::let_stmt(pattern, ty, Some(initializer)); + if stmts.len() > 0 { + let block = make::block_expr(stmts, None); + format!( + "{}\n {}", + update_expr_string(block.to_string()), + let_stmt.to_string() + ) + } else { + let_stmt.to_string() + } + } + None => { + let empty_tuple = make::expr_tuple([]); + make::let_stmt(pattern, ty, Some(empty_tuple)).to_string() + } + }; + acc.add(assist_id, assist_label, target, |builder| { + builder.replace(parent.syntax().text_range(), replaced); + }) + } else { + let parent = ast::Expr::cast(parent)?; + match parent.clone() { + ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::LoopExpr(_) => (), + ast::Expr::MatchExpr(_) => block = block.dedent(IndentLevel(1)), + ast::Expr::IfExpr(if_expr) => { + let then_branch = if_expr.then_branch()?; + if then_branch == block { + if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) { + // For `else if` blocks + let ancestor_then_branch = ancestor.then_branch()?; + + return acc.add(assist_id, assist_label, target, |edit| { + let range_to_del_else_if = TextRange::new( + ancestor_then_branch.syntax().text_range().end(), + l_curly_token.text_range().start(), + ); + let range_to_del_rest = TextRange::new( + then_branch.syntax().text_range().end(), + if_expr.syntax().text_range().end(), + ); + + edit.delete(range_to_del_rest); + edit.delete(range_to_del_else_if); + edit.replace( + target, + update_expr_string_without_newline(then_branch.to_string()), + ); + }); + } + } else { return acc.add(assist_id, assist_label, target, |edit| { - let range_to_del_else_if = TextRange::new( - ancestor_then_branch.syntax().text_range().end(), - l_curly_token.text_range().start(), - ); - let range_to_del_rest = TextRange::new( + let range_to_del = TextRange::new( then_branch.syntax().text_range().end(), - if_expr.syntax().text_range().end(), + l_curly_token.text_range().start(), ); - edit.delete(range_to_del_rest); - edit.delete(range_to_del_else_if); - edit.replace( - target, - update_expr_string_without_newline(then_branch.to_string()), - ); + edit.delete(range_to_del); + edit.replace(target, update_expr_string_without_newline(block.to_string())); }); } - } else { - return acc.add(assist_id, assist_label, target, |edit| { - let range_to_del = TextRange::new( - then_branch.syntax().text_range().end(), - l_curly_token.text_range().start(), - ); - - edit.delete(range_to_del); - edit.replace(target, update_expr_string_without_newline(block.to_string())); - }); } - } - _ => return None, - }; + _ => return None, + }; - acc.add(assist_id, assist_label, target, |builder| { - builder.replace(parent.syntax().text_range(), update_expr_string(block.to_string())); - }) + acc.add(assist_id, assist_label, target, |builder| { + builder.replace(parent.syntax().text_range(), update_expr_string(block.to_string())); + }) + } } fn update_expr_string(expr_string: String) -> String { @@ -725,6 +754,19 @@ fn main() -> i32 { unwrap_block, r#" fn main() { + let x = {$0}; +} +"#, + r#" +fn main() { + let x = (); +} +"#, + ); + check_assist( + unwrap_block, + r#" +fn main() { let x = {$0 bar }; @@ -736,6 +778,34 @@ fn main() { } "#, ); + check_assist( + unwrap_block, + r#" +fn main() -> i32 { + let _ = {$01; 2}; +} +"#, + r#" +fn main() -> i32 { + 1; + let _ = 2; +} +"#, + ); + check_assist( + unwrap_block, + r#" +fn main() -> i32 { + let mut a = {$01; 2}; +} +"#, + r#" +fn main() -> i32 { + 1; + let mut a = 2; +} +"#, + ); } #[test] diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs index 7813c9f9c..276cf5f5d 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs @@ -106,6 +106,7 @@ mod handlers { pub(crate) type Handler = fn(&mut Assists, &AssistContext<'_>) -> Option<()>; + mod add_braces; mod add_explicit_type; mod add_label_to_loop; mod add_lifetime_to_type; @@ -126,6 +127,7 @@ mod handlers { mod convert_to_guarded_return; mod convert_two_arm_bool_match_to_matches_macro; mod convert_while_to_loop; + mod desugar_doc_comment; mod destructure_tuple_binding; mod expand_glob_import; mod extract_expressions_from_format_string; @@ -208,6 +210,7 @@ mod handlers { pub(crate) fn all() -> &'static [Handler] { &[ // These are alphabetic for the foolish consistency + add_braces::add_braces, add_explicit_type::add_explicit_type, add_label_to_loop::add_label_to_loop, add_missing_match_arms::add_missing_match_arms, @@ -231,6 +234,7 @@ mod handlers { convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct, convert_two_arm_bool_match_to_matches_macro::convert_two_arm_bool_match_to_matches_macro, convert_while_to_loop::convert_while_to_loop, + desugar_doc_comment::desugar_doc_comment, destructure_tuple_binding::destructure_tuple_binding, expand_glob_import::expand_glob_import, extract_expressions_from_format_string::extract_expressions_from_format_string, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs index fca268a1f..94be99fd7 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs @@ -33,6 +33,20 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig { assist_emit_must_use: false, }; +pub(crate) const TEST_CONFIG_NO_SNIPPET_CAP: AssistConfig = AssistConfig { + snippet_cap: None, + allowed: None, + insert_use: InsertUseConfig { + granularity: ImportGranularity::Crate, + prefix_kind: hir::PrefixKind::Plain, + enforce_granularity: true, + group: true, + skip_glob_imports: true, + }, + prefer_no_std: false, + assist_emit_must_use: false, +}; + pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { RootDatabase::with_single_file(text) } @@ -43,6 +57,22 @@ pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_ check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), None); } +#[track_caller] +pub(crate) fn check_assist_no_snippet_cap( + assist: Handler, + ra_fixture_before: &str, + ra_fixture_after: &str, +) { + let ra_fixture_after = trim_indent(ra_fixture_after); + check_with_config( + TEST_CONFIG_NO_SNIPPET_CAP, + assist, + ra_fixture_before, + ExpectedResult::After(&ra_fixture_after), + None, + ); +} + // There is no way to choose what assist within a group you want to test against, // so this is here to allow you choose. pub(crate) fn check_assist_by_label( @@ -119,6 +149,17 @@ enum ExpectedResult<'a> { #[track_caller] fn check(handler: Handler, before: &str, expected: ExpectedResult<'_>, assist_label: Option<&str>) { + check_with_config(TEST_CONFIG, handler, before, expected, assist_label); +} + +#[track_caller] +fn check_with_config( + config: AssistConfig, + handler: Handler, + before: &str, + expected: ExpectedResult<'_>, + assist_label: Option<&str>, +) { let (mut db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before); db.set_enable_proc_attr_macros(true); let text_without_caret = db.file_text(file_with_caret_id).to_string(); @@ -126,7 +167,6 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult<'_>, assist_la let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; let sema = Semantics::new(&db); - let config = TEST_CONFIG; let ctx = AssistContext::new(sema, &config, frange); let resolve = match expected { ExpectedResult::Unresolved => AssistResolveStrategy::None, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs index 006ae4b30..8a25e1f64 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs @@ -3,6 +3,31 @@ use super::check_doc_test; #[test] +fn doctest_add_braces() { + check_doc_test( + "add_braces", + r#####" +fn foo(n: i32) -> i32 { + match n { + 1 =>$0 n + 1, + _ => 0 + } +} +"#####, + r#####" +fn foo(n: i32) -> i32 { + match n { + 1 => { + n + 1 + }, + _ => 0 + } +} +"#####, + ) +} + +#[test] fn doctest_add_explicit_type() { check_doc_test( "add_explicit_type", @@ -598,6 +623,21 @@ fn main() { } #[test] +fn doctest_desugar_doc_comment() { + check_doc_test( + "desugar_doc_comment", + r#####" +/// Multi-line$0 +/// comment +"#####, + r#####" +#[doc = r"Multi-line +comment"] +"#####, + ) +} + +#[test] fn doctest_expand_glob_import() { check_doc_test( "expand_glob_import", diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs index 7add66064..f323ebcf7 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -208,23 +208,6 @@ pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor } } -/// Escapes text that should be rendered as-is, typically those that we're copy-pasting what the -/// users wrote. -/// -/// This function should only be used when the text doesn't contain snippet **AND** the text -/// wouldn't be included in a snippet. -pub(crate) fn escape_non_snippet(text: &mut String) { - // While we *can* escape `}`, we don't really have to in this specific case. We only need to - // escape it inside `${}` to disambiguate it from the ending token of the syntax, but after we - // escape every occurrence of `$`, we wouldn't have `${}` in the first place. - // - // This will break if the text contains snippet or it will be included in a snippet (hence doc - // comment). Compare `fn escape(buf)` in `render_snippet()` above, where the escaped text is - // included in a snippet. - stdx::replace(text, '\\', r"\\"); - stdx::replace(text, '$', r"\$"); -} - pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize { node.children_with_tokens() .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR)) @@ -758,3 +741,24 @@ pub(crate) fn convert_param_list_to_arg_list(list: ast::ParamList) -> ast::ArgLi } make::arg_list(args) } + +/// Calculate the number of hashes required for a raw string containing `s` +pub(crate) fn required_hashes(s: &str) -> usize { + let mut res = 0usize; + for idx in s.match_indices('"').map(|(i, _)| i) { + let (_, sub) = s.split_at(idx + 1); + let n_hashes = sub.chars().take_while(|c| *c == '#').count(); + res = res.max(n_hashes + 1) + } + res +} +#[test] +fn test_required_hashes() { + assert_eq!(0, required_hashes("abc")); + assert_eq!(0, required_hashes("###")); + assert_eq!(1, required_hashes("\"")); + assert_eq!(2, required_hashes("\"#abc")); + assert_eq!(0, required_hashes("#abc")); + assert_eq!(3, required_hashes("#ab\"##c")); + assert_eq!(5, required_hashes("#ab\"##\"####c")); +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs index d4abb5125..808b23405 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs @@ -1,5 +1,6 @@ //! This module contains functions to generate default trait impl function bodies where possible. +use hir::TraitRef; use syntax::{ ast::{self, edit::AstNodeEdit, make, AstNode, BinaryOp, CmpOp, HasName, LogicOp}, ted, @@ -7,6 +8,8 @@ use syntax::{ /// Generate custom trait bodies without default implementation where possible. /// +/// If `func` is defined within an existing impl block, pass [`TraitRef`]. Otherwise pass `None`. +/// /// Returns `Option` so that we can use `?` rather than `if let Some`. Returning /// `None` means that generating a custom trait body failed, and the body will remain /// as `todo!` instead. @@ -14,14 +17,15 @@ pub(crate) fn gen_trait_fn_body( func: &ast::Fn, trait_path: &ast::Path, adt: &ast::Adt, + trait_ref: Option<TraitRef>, ) -> Option<()> { match trait_path.segment()?.name_ref()?.text().as_str() { "Clone" => gen_clone_impl(adt, func), "Debug" => gen_debug_impl(adt, func), "Default" => gen_default_impl(adt, func), "Hash" => gen_hash_impl(adt, func), - "PartialEq" => gen_partial_eq(adt, func), - "PartialOrd" => gen_partial_ord(adt, func), + "PartialEq" => gen_partial_eq(adt, func, trait_ref), + "PartialOrd" => gen_partial_ord(adt, func, trait_ref), _ => None, } } @@ -395,7 +399,7 @@ fn gen_hash_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { } /// Generate a `PartialEq` impl based on the fields and members of the target type. -fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { +fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef>) -> Option<()> { stdx::always!(func.name().map_or(false, |name| name.text() == "eq")); fn gen_eq_chain(expr: Option<ast::Expr>, cmp: ast::Expr) -> Option<ast::Expr> { match expr { @@ -423,8 +427,15 @@ fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { ast::Pat::IdentPat(make::ident_pat(false, false, make::name(field_name))) } - // FIXME: return `None` if the trait carries a generic type; we can only - // generate this code `Self` for the time being. + // Check that self type and rhs type match. We don't know how to implement the method + // automatically otherwise. + if let Some(trait_ref) = trait_ref { + let self_ty = trait_ref.self_ty(); + let rhs_ty = trait_ref.get_type_argument(1)?; + if self_ty != rhs_ty { + return None; + } + } let body = match adt { // `PartialEq` cannot be derived for unions, so no default impl can be provided. @@ -568,7 +579,7 @@ fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { make::block_expr(None, expr).indent(ast::edit::IndentLevel(1)) } - // No fields in the body means there's nothing to hash. + // No fields in the body means there's nothing to compare. None => { let expr = make::expr_literal("true").into(); make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1)) @@ -580,7 +591,7 @@ fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { Some(()) } -fn gen_partial_ord(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { +fn gen_partial_ord(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef>) -> Option<()> { stdx::always!(func.name().map_or(false, |name| name.text() == "partial_cmp")); fn gen_partial_eq_match(match_target: ast::Expr) -> Option<ast::Stmt> { let mut arms = vec![]; @@ -605,8 +616,15 @@ fn gen_partial_ord(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { make::expr_method_call(lhs, method, make::arg_list(Some(rhs))) } - // FIXME: return `None` if the trait carries a generic type; we can only - // generate this code `Self` for the time being. + // Check that self type and rhs type match. We don't know how to implement the method + // automatically otherwise. + if let Some(trait_ref) = trait_ref { + let self_ty = trait_ref.self_ty(); + let rhs_ty = trait_ref.get_type_argument(1)?; + if self_ty != rhs_ty { + return None; + } + } let body = match adt { // `PartialOrd` cannot be derived for unions, so no default impl can be provided. diff --git a/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml b/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml index 11310e2f1..092fb3036 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml +++ b/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml @@ -2,9 +2,11 @@ name = "ide-completion" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -13,21 +15,23 @@ doctest = false cov-mark = "2.0.0-pre.1" itertools = "0.10.5" -once_cell = "1.15.0" -smallvec = "1.10.0" +once_cell = "1.17.0" +smallvec.workspace = true -stdx = { path = "../stdx", version = "0.0.0" } -syntax = { path = "../syntax", version = "0.0.0" } -text-edit = { path = "../text-edit", version = "0.0.0" } -base-db = { path = "../base-db", version = "0.0.0" } -ide-db = { path = "../ide-db", version = "0.0.0" } -profile = { path = "../profile", version = "0.0.0" } +# local deps +base-db.workspace = true +ide-db.workspace = true +profile.workspace = true +stdx.workspace = true +syntax.workspace = true +text-edit.workspace = true # completions crate should depend only on the top-level `hir` package. if you need # something from some `hir-xxx` subpackage, reexport the API via `hir`. -hir = { path = "../hir", version = "0.0.0" } +hir.workspace = true [dev-dependencies] expect-test = "1.4.0" -test-utils = { path = "../test-utils" } +# local deps +test-utils.workspace = true diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs index 9a060857e..889d90095 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -869,7 +869,7 @@ impl Test for T {{ }; // Enumerate some possible next siblings. - for next_sibling in &[ + for next_sibling in [ "", "fn other_fn() {}", // `const $0 fn` -> `const fn` "type OtherType = i32;", diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs index 1d03c8cc5..b9ab2afca 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs @@ -86,6 +86,7 @@ fn foo(a: A) { a.$0 } sn match match expr {} sn ref &expr sn refm &mut expr + sn unsafe unsafe {} "#]], ); @@ -110,6 +111,7 @@ fn foo() { sn match match expr {} sn ref &expr sn refm &mut expr + sn unsafe unsafe {} "#]], ); } @@ -136,6 +138,7 @@ fn foo(a: A) { a.$0 } sn match match expr {} sn ref &expr sn refm &mut expr + sn unsafe unsafe {} "#]], ); } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs index f4f37d77d..c55bd9aaa 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs @@ -6,7 +6,7 @@ use hir::{Documentation, HasAttrs}; use ide_db::{imports::insert_use::ImportScope, ty_filter::TryEnum, SnippetCap}; use syntax::{ ast::{self, make, AstNode, AstToken}, - SyntaxKind::{EXPR_STMT, STMT_LIST}, + SyntaxKind::{BLOCK_EXPR, EXPR_STMT, FOR_EXPR, IF_EXPR, LOOP_EXPR, STMT_LIST, WHILE_EXPR}, TextRange, TextSize, }; use text_edit::TextEdit; @@ -123,6 +123,22 @@ pub(crate) fn complete_postfix( postfix_snippet("ref", "&expr", &format!("&{receiver_text}")).add_to(acc); postfix_snippet("refm", "&mut expr", &format!("&mut {receiver_text}")).add_to(acc); + let mut unsafe_should_be_wrapped = true; + if dot_receiver.syntax().kind() == BLOCK_EXPR { + unsafe_should_be_wrapped = false; + if let Some(parent) = dot_receiver.syntax().parent() { + if matches!(parent.kind(), IF_EXPR | WHILE_EXPR | LOOP_EXPR | FOR_EXPR) { + unsafe_should_be_wrapped = true; + } + } + }; + let unsafe_completion_string = if unsafe_should_be_wrapped { + format!("unsafe {{ {receiver_text} }}") + } else { + format!("unsafe {receiver_text}") + }; + postfix_snippet("unsafe", "unsafe {}", &unsafe_completion_string).add_to(acc); + // The rest of the postfix completions create an expression that moves an argument, // so it's better to consider references now to avoid breaking the compilation @@ -329,18 +345,19 @@ fn main() { } "#, expect![[r#" - sn box Box::new(expr) - sn call function(expr) - sn dbg dbg!(expr) - sn dbgr dbg!(&expr) - sn if if expr {} - sn let let - sn letm let mut - sn match match expr {} - sn not !expr - sn ref &expr - sn refm &mut expr - sn while while expr {} + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn if if expr {} + sn let let + sn letm let mut + sn match match expr {} + sn not !expr + sn ref &expr + sn refm &mut expr + sn unsafe unsafe {} + sn while while expr {} "#]], ); } @@ -359,16 +376,17 @@ fn main() { } "#, expect![[r#" - sn box Box::new(expr) - sn call function(expr) - sn dbg dbg!(expr) - sn dbgr dbg!(&expr) - sn if if expr {} - sn match match expr {} - sn not !expr - sn ref &expr - sn refm &mut expr - sn while while expr {} + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn if if expr {} + sn match match expr {} + sn not !expr + sn ref &expr + sn refm &mut expr + sn unsafe unsafe {} + sn while while expr {} "#]], ); } @@ -383,15 +401,16 @@ fn main() { } "#, expect![[r#" - sn box Box::new(expr) - sn call function(expr) - sn dbg dbg!(expr) - sn dbgr dbg!(&expr) - sn let let - sn letm let mut - sn match match expr {} - sn ref &expr - sn refm &mut expr + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn unsafe unsafe {} "#]], ) } @@ -406,18 +425,19 @@ fn main() { } "#, expect![[r#" - sn box Box::new(expr) - sn call function(expr) - sn dbg dbg!(expr) - sn dbgr dbg!(&expr) - sn if if expr {} - sn let let - sn letm let mut - sn match match expr {} - sn not !expr - sn ref &expr - sn refm &mut expr - sn while while expr {} + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn if if expr {} + sn let let + sn letm let mut + sn match match expr {} + sn not !expr + sn ref &expr + sn refm &mut expr + sn unsafe unsafe {} + sn while while expr {} "#]], ); } @@ -518,6 +538,49 @@ fn main() { } #[test] + fn postfix_completion_for_unsafe() { + check_edit("unsafe", r#"fn main() { foo.$0 }"#, r#"fn main() { unsafe { foo } }"#); + check_edit("unsafe", r#"fn main() { { foo }.$0 }"#, r#"fn main() { unsafe { foo } }"#); + check_edit( + "unsafe", + r#"fn main() { if x { foo }.$0 }"#, + r#"fn main() { unsafe { if x { foo } } }"#, + ); + check_edit( + "unsafe", + r#"fn main() { loop { foo }.$0 }"#, + r#"fn main() { unsafe { loop { foo } } }"#, + ); + check_edit( + "unsafe", + r#"fn main() { if true {}.$0 }"#, + r#"fn main() { unsafe { if true {} } }"#, + ); + check_edit( + "unsafe", + r#"fn main() { while true {}.$0 }"#, + r#"fn main() { unsafe { while true {} } }"#, + ); + check_edit( + "unsafe", + r#"fn main() { for i in 0..10 {}.$0 }"#, + r#"fn main() { unsafe { for i in 0..10 {} } }"#, + ); + check_edit( + "unsafe", + r#"fn main() { let x = if true {1} else {2}.$0 }"#, + r#"fn main() { let x = unsafe { if true {1} else {2} } }"#, + ); + + // completion will not be triggered + check_edit( + "unsafe", + r#"fn main() { let x = true else {panic!()}.$0}"#, + r#"fn main() { let x = true else {panic!()}.unsafe}"#, + ); + } + + #[test] fn custom_postfix_completion() { let config = CompletionConfig { snippets: vec![Snippet::new( @@ -684,4 +747,16 @@ fn main() { "#, ); } + + #[test] + fn no_postfix_completions_in_if_block_that_has_an_else() { + check( + r#" +fn test() { + if true {}.$0 else {} +} +"#, + expect![[r#""#]], + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/config.rs b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs index a0f5e81b4..8f6a97e1e 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/config.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs @@ -19,6 +19,7 @@ pub struct CompletionConfig { pub insert_use: InsertUseConfig, pub prefer_no_std: bool, pub snippets: Vec<Snippet>, + pub limit: Option<usize>, } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs index aa77f4495..ea54068b0 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs @@ -571,28 +571,25 @@ impl<'a> CompletionContext<'a> { // try to skip completions on path with invalid colons // this approach works in normal path and inside token tree - match original_token.kind() { - T![:] => { - // return if no prev token before colon - let prev_token = original_token.prev_token()?; - - // only has a single colon - if prev_token.kind() != T![:] { - return None; - } + if original_token.kind() == T![:] { + // return if no prev token before colon + let prev_token = original_token.prev_token()?; - // has 3 colon or 2 coloncolon in a row - // special casing this as per discussion in https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1031845205 - // and https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1032812751 - if prev_token - .prev_token() - .map(|t| t.kind() == T![:] || t.kind() == T![::]) - .unwrap_or(false) - { - return None; - } + // only has a single colon + if prev_token.kind() != T![:] { + return None; + } + + // has 3 colon or 2 coloncolon in a row + // special casing this as per discussion in https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1031845205 + // and https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1032812751 + if prev_token + .prev_token() + .map(|t| t.kind() == T![:] || t.kind() == T![::]) + .unwrap_or(false) + { + return None; } - _ => {} } let AnalysisResult { diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs index e34824e22..db0045aef 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs @@ -29,6 +29,7 @@ pub(super) struct AnalysisResult { pub(super) analysis: CompletionAnalysis, pub(super) expected: (Option<Type>, Option<ast::NameOrNameRef>), pub(super) qualifier_ctx: QualifierCtx, + /// the original token of the expanded file pub(super) token: SyntaxToken, pub(super) offset: TextSize, } @@ -48,7 +49,9 @@ pub(super) fn expand_and_analyze( // make the offset point to the start of the original token, as that is what the // intermediate offsets calculated in expansion always points to let offset = offset - relative_offset; - let expansion = expand(sema, original_file, speculative_file, offset, fake_ident_token); + let expansion = + expand(sema, original_file, speculative_file, offset, fake_ident_token, relative_offset); + // add the relative offset back, so that left_biased finds the proper token let offset = expansion.offset + relative_offset; let token = expansion.original_file.token_at_offset(offset).left_biased()?; @@ -67,6 +70,7 @@ fn expand( mut speculative_file: SyntaxNode, mut offset: TextSize, mut fake_ident_token: SyntaxToken, + relative_offset: TextSize, ) -> ExpansionResult { let _p = profile::span("CompletionContext::expand"); let mut derive_ctx = None; @@ -97,7 +101,7 @@ fn expand( // successful expansions (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => { let new_offset = fake_mapped_token.text_range().start(); - if new_offset > actual_expansion.text_range().end() { + if new_offset + relative_offset > actual_expansion.text_range().end() { // offset outside of bounds from the original expansion, // stop here to prevent problems from happening break 'expansion; @@ -176,7 +180,7 @@ fn expand( // successful expansions (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => { let new_offset = fake_mapped_token.text_range().start(); - if new_offset > actual_expansion.text_range().end() { + if new_offset + relative_offset > actual_expansion.text_range().end() { // offset outside of bounds from the original expansion, // stop here to prevent problems from happening break 'expansion; @@ -210,15 +214,6 @@ fn analyze( let _p = profile::span("CompletionContext::analyze"); let ExpansionResult { original_file, speculative_file, offset, fake_ident_token, derive_ctx } = expansion_result; - let syntax_element = NodeOrToken::Token(fake_ident_token); - if is_in_token_of_for_loop(syntax_element.clone()) { - // for pat $0 - // there is nothing to complete here except `in` keyword - // don't bother populating the context - // FIXME: the completion calculations should end up good enough - // such that this special case becomes unnecessary - return None; - } // Overwrite the path kind for derives if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx { @@ -246,37 +241,35 @@ fn analyze( return None; } - let name_like = match find_node_at_offset(&speculative_file, offset) { - Some(it) => it, - None => { - let analysis = if let Some(original) = ast::String::cast(original_token.clone()) { - CompletionAnalysis::String { - original, - expanded: ast::String::cast(self_token.clone()), + let Some(name_like) = find_node_at_offset(&speculative_file, offset) else { + let analysis = if let Some(original) = ast::String::cast(original_token.clone()) { + CompletionAnalysis::String { + original, + expanded: ast::String::cast(self_token.clone()), + } + } else { + // Fix up trailing whitespace problem + // #[attr(foo = $0 + let token = syntax::algo::skip_trivia_token(self_token.clone(), Direction::Prev)?; + let p = token.parent()?; + if p.kind() == SyntaxKind::TOKEN_TREE + && p.ancestors().any(|it| it.kind() == SyntaxKind::META) + { + let colon_prefix = previous_non_trivia_token(self_token.clone()) + .map_or(false, |it| T![:] == it.kind()); + CompletionAnalysis::UnexpandedAttrTT { + fake_attribute_under_caret: fake_ident_token + .parent_ancestors() + .find_map(ast::Attr::cast), + colon_prefix, } } else { - // Fix up trailing whitespace problem - // #[attr(foo = $0 - let token = syntax::algo::skip_trivia_token(self_token.clone(), Direction::Prev)?; - let p = token.parent()?; - if p.kind() == SyntaxKind::TOKEN_TREE - && p.ancestors().any(|it| it.kind() == SyntaxKind::META) - { - let colon_prefix = previous_non_trivia_token(self_token.clone()) - .map_or(false, |it| T![:] == it.kind()); - CompletionAnalysis::UnexpandedAttrTT { - fake_attribute_under_caret: syntax_element - .ancestors() - .find_map(ast::Attr::cast), - colon_prefix, - } - } else { - return None; - } - }; - return Some((analysis, (None, None), QualifierCtx::default())); - } + return None; + } + }; + return Some((analysis, (None, None), QualifierCtx::default())); }; + let expected = expected_type_and_name(sema, self_token, &name_like); let mut qual_ctx = QualifierCtx::default(); let analysis = match name_like { @@ -287,6 +280,22 @@ fn analyze( let parent = name_ref.syntax().parent()?; let (nameref_ctx, qualifier_ctx) = classify_name_ref(sema, &original_file, name_ref, parent)?; + + if let NameRefContext { + kind: + NameRefKind::Path(PathCompletionCtx { kind: PathKind::Expr { .. }, path, .. }, ..), + .. + } = &nameref_ctx + { + if is_in_token_of_for_loop(path) { + // for pat $0 + // there is nothing to complete here except `in` keyword + // don't bother populating the context + // Ideally this special casing wouldn't be needed, but the parser recovers + return None; + } + } + qual_ctx = qualifier_ctx; CompletionAnalysis::NameRef(nameref_ctx) } @@ -320,16 +329,14 @@ fn expected_type_and_name( ast::FieldExpr(e) => e .syntax() .ancestors() - .map_while(ast::FieldExpr::cast) - .last() - .map(|it| it.syntax().clone()), + .take_while(|it| ast::FieldExpr::can_cast(it.kind())) + .last(), ast::PathSegment(e) => e .syntax() .ancestors() .skip(1) .take_while(|it| ast::Path::can_cast(it.kind()) || ast::PathExpr::can_cast(it.kind())) - .find_map(ast::PathExpr::cast) - .map(|it| it.syntax().clone()), + .find(|it| ast::PathExpr::can_cast(it.kind())), _ => None } }; @@ -602,6 +609,18 @@ fn classify_name_ref( }, _ => false, }; + + let reciever_is_part_of_indivisible_expression = match &receiver { + Some(ast::Expr::IfExpr(_)) => { + let next_token_kind = next_non_trivia_token(name_ref.syntax().clone()).map(|t| t.kind()); + next_token_kind == Some(SyntaxKind::ELSE_KW) + }, + _ => false + }; + if reciever_is_part_of_indivisible_expression { + return None; + } + let kind = NameRefKind::DotAccess(DotAccess { receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)), kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal }, @@ -653,8 +672,15 @@ fn classify_name_ref( }; let after_if_expr = |node: SyntaxNode| { let prev_expr = (|| { + let node = match node.parent().and_then(ast::ExprStmt::cast) { + Some(stmt) => stmt.syntax().clone(), + None => node, + }; let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?; - ast::ExprStmt::cast(prev_sibling)?.expr() + + ast::ExprStmt::cast(prev_sibling.clone()) + .and_then(|it| it.expr()) + .or_else(|| ast::Expr::cast(prev_sibling)) })(); matches!(prev_expr, Some(ast::Expr::IfExpr(_))) }; @@ -672,10 +698,10 @@ fn classify_name_ref( { if let Some(item) = ast::Item::cast(n) { let is_inbetween = match &item { - ast::Item::Const(it) => it.body().is_none(), + ast::Item::Const(it) => it.body().is_none() && it.semicolon_token().is_none(), ast::Item::Enum(it) => it.variant_list().is_none(), ast::Item::ExternBlock(it) => it.extern_item_list().is_none(), - ast::Item::Fn(it) => it.body().is_none(), + ast::Item::Fn(it) => it.body().is_none() && it.semicolon_token().is_none(), ast::Item::Impl(it) => it.assoc_item_list().is_none(), ast::Item::Module(it) => { it.item_list().is_none() && it.semicolon_token().is_none() @@ -685,7 +711,7 @@ fn classify_name_ref( it.field_list().is_none() && it.semicolon_token().is_none() } ast::Item::Trait(it) => it.assoc_item_list().is_none(), - ast::Item::TypeAlias(it) => it.ty().is_none(), + ast::Item::TypeAlias(it) => it.ty().is_none() && it.semicolon_token().is_none(), ast::Item::Union(it) => it.record_field_list().is_none(), _ => false, }; @@ -1248,40 +1274,29 @@ fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> { Some((use_tree.path()?, true)) } -pub(crate) fn is_in_token_of_for_loop(element: SyntaxElement) -> bool { +fn is_in_token_of_for_loop(path: &ast::Path) -> bool { // oh my ... (|| { - let syntax_token = element.into_token()?; - let range = syntax_token.text_range(); - let for_expr = syntax_token.parent_ancestors().find_map(ast::ForExpr::cast)?; - - // check if the current token is the `in` token of a for loop - if let Some(token) = for_expr.in_token() { - return Some(syntax_token == token); + let expr = path.syntax().parent().and_then(ast::PathExpr::cast)?; + let for_expr = expr.syntax().parent().and_then(ast::ForExpr::cast)?; + if for_expr.in_token().is_some() { + return Some(false); } let pat = for_expr.pat()?; - if range.end() < pat.syntax().text_range().end() { - // if we are inside or before the pattern we can't be at the `in` token position - return None; - } let next_sibl = next_non_trivia_sibling(pat.syntax().clone().into())?; Some(match next_sibl { - // the loop body is some node, if our token is at the start we are at the `in` position, - // otherwise we could be in a recovered expression, we don't wanna ruin completions there - syntax::NodeOrToken::Node(n) => n.text_range().start() == range.start(), - // the loop body consists of a single token, if we are this we are certainly at the `in` token position - syntax::NodeOrToken::Token(t) => t == syntax_token, + syntax::NodeOrToken::Node(n) => { + n.text_range().start() == path.syntax().text_range().start() + } + syntax::NodeOrToken::Token(t) => { + t.text_range().start() == path.syntax().text_range().start() + } }) })() .unwrap_or(false) } -#[test] -fn test_for_is_prev2() { - crate::tests::check_pattern_is_applicable(r"fn __() { for i i$0 }", is_in_token_of_for_loop); -} - -pub(crate) fn is_in_loop_body(node: &SyntaxNode) -> bool { +fn is_in_loop_body(node: &SyntaxNode) -> bool { node.ancestors() .take_while(|it| it.kind() != SyntaxKind::FN && it.kind() != SyntaxKind::CLOSURE_EXPR) .find_map(|it| { @@ -1314,6 +1329,22 @@ fn previous_non_trivia_token(e: impl Into<SyntaxElement>) -> Option<SyntaxToken> None } +fn next_non_trivia_token(e: impl Into<SyntaxElement>) -> Option<SyntaxToken> { + let mut token = match e.into() { + SyntaxElement::Node(n) => n.last_token()?, + SyntaxElement::Token(t) => t, + } + .next_token(); + while let Some(inner) = token { + if !inner.kind().is_trivia() { + return Some(inner); + } else { + token = inner.next_token(); + } + } + None +} + fn next_non_trivia_sibling(ele: SyntaxElement) -> Option<SyntaxElement> { let mut e = ele.next_sibling_or_token(); while let Some(inner) = e { diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs index 657eab5b1..2f65491d8 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs @@ -14,13 +14,14 @@ use crate::{ render::{render_path_resolution, RenderContext}, }; -/// `CompletionItem` describes a single completion variant in the editor pop-up. -/// It is basically a POD with various properties. To construct a -/// `CompletionItem`, use `new` method and the `Builder` struct. +/// `CompletionItem` describes a single completion entity which expands to 1 or more entries in the +/// editor pop-up. It is basically a POD with various properties. To construct a +/// [`CompletionItem`], use [`Builder::new`] method and the [`Builder`] struct. #[derive(Clone)] +#[non_exhaustive] pub struct CompletionItem { /// Label in the completion pop up which identifies completion. - label: SmolStr, + pub label: SmolStr, /// Range of identifier that is being completed. /// /// It should be used primarily for UI, but we also use this to convert @@ -29,33 +30,33 @@ pub struct CompletionItem { /// `source_range` must contain the completion offset. `text_edit` should /// start with what `source_range` points to, or VSCode will filter out the /// completion silently. - source_range: TextRange, + pub source_range: TextRange, /// What happens when user selects this item. /// /// Typically, replaces `source_range` with new identifier. - text_edit: TextEdit, - is_snippet: bool, + pub text_edit: TextEdit, + pub is_snippet: bool, /// What item (struct, function, etc) are we completing. - kind: CompletionItemKind, + pub kind: CompletionItemKind, /// Lookup is used to check if completion item indeed can complete current /// ident. /// /// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it /// contains `bar` sub sequence), and `quux` will rejected. - lookup: Option<SmolStr>, + pub lookup: Option<SmolStr>, /// Additional info to show in the UI pop up. - detail: Option<String>, - documentation: Option<Documentation>, + pub detail: Option<String>, + pub documentation: Option<Documentation>, /// Whether this item is marked as deprecated - deprecated: bool, + pub deprecated: bool, /// If completing a function call, ask the editor to show parameter popup /// after completion. - trigger_call_info: bool, + pub trigger_call_info: bool, /// We use this to sort completion. Relevance records facts like "do the /// types align precisely?". We can't sort by relevances directly, they are @@ -64,36 +65,39 @@ pub struct CompletionItem { /// Note that Relevance ignores fuzzy match score. We compute Relevance for /// all possible items, and then separately build an ordered completion list /// based on relevance and fuzzy matching with the already typed identifier. - relevance: CompletionRelevance, + pub relevance: CompletionRelevance, /// Indicates that a reference or mutable reference to this variable is a /// possible match. - ref_match: Option<(Mutability, TextSize)>, + // FIXME: We shouldn't expose Mutability here (that is HIR types at all), its fine for now though + // until we have more splitting completions in which case we should think about + // generalizing this. See https://github.com/rust-lang/rust-analyzer/issues/12571 + pub ref_match: Option<(Mutability, TextSize)>, /// The import data to add to completion's edits. - import_to_add: SmallVec<[LocatedImport; 1]>, + pub import_to_add: SmallVec<[LocatedImport; 1]>, } // We use custom debug for CompletionItem to make snapshot tests more readable. impl fmt::Debug for CompletionItem { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut s = f.debug_struct("CompletionItem"); - s.field("label", &self.label()).field("source_range", &self.source_range()); - if self.text_edit().len() == 1 { - let atom = &self.text_edit().iter().next().unwrap(); + s.field("label", &self.label).field("source_range", &self.source_range); + if self.text_edit.len() == 1 { + let atom = &self.text_edit.iter().next().unwrap(); s.field("delete", &atom.delete); s.field("insert", &atom.insert); } else { s.field("text_edit", &self.text_edit); } - s.field("kind", &self.kind()); - if self.lookup() != self.label() { + s.field("kind", &self.kind); + if self.lookup() != self.label { s.field("lookup", &self.lookup()); } - if let Some(detail) = self.detail() { + if let Some(detail) = &self.detail { s.field("detail", &detail); } - if let Some(documentation) = self.documentation() { + if let Some(documentation) = &self.documentation { s.field("documentation", &documentation); } if self.deprecated { @@ -351,63 +355,25 @@ impl CompletionItem { } } - /// What user sees in pop-up in the UI. - pub fn label(&self) -> &str { - &self.label - } - pub fn source_range(&self) -> TextRange { - self.source_range - } - - pub fn text_edit(&self) -> &TextEdit { - &self.text_edit - } - /// Whether `text_edit` is a snippet (contains `$0` markers). - pub fn is_snippet(&self) -> bool { - self.is_snippet - } - - /// Short one-line additional information, like a type - pub fn detail(&self) -> Option<&str> { - self.detail.as_deref() - } - /// A doc-comment - pub fn documentation(&self) -> Option<Documentation> { - self.documentation.clone() - } /// What string is used for filtering. pub fn lookup(&self) -> &str { self.lookup.as_deref().unwrap_or(&self.label) } - pub fn kind(&self) -> CompletionItemKind { - self.kind - } - - pub fn deprecated(&self) -> bool { - self.deprecated - } - - pub fn relevance(&self) -> CompletionRelevance { - self.relevance - } - - pub fn trigger_call_info(&self) -> bool { - self.trigger_call_info - } - - pub fn ref_match(&self) -> Option<(Mutability, TextSize, CompletionRelevance)> { + pub fn ref_match(&self) -> Option<(String, text_edit::Indel, CompletionRelevance)> { // Relevance of the ref match should be the same as the original // match, but with exact type match set because self.ref_match // is only set if there is an exact type match. let mut relevance = self.relevance; relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact); - self.ref_match.map(|(mutability, offset)| (mutability, offset, relevance)) - } - - pub fn imports_to_add(&self) -> &[LocatedImport] { - &self.import_to_add + self.ref_match.map(|(mutability, offset)| { + ( + format!("&{}{}", mutability.as_keyword_for_ref(), self.label), + text_edit::Indel::insert(offset, format!("&{}", mutability.as_keyword_for_ref())), + relevance, + ) + }) } } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs index 4b48ec6bc..6fe781114 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs @@ -156,13 +156,15 @@ pub fn completions( // prevent `(` from triggering unwanted completion noise if trigger_character == Some('(') { - if let CompletionAnalysis::NameRef(NameRefContext { kind, .. }) = &analysis { - if let NameRefKind::Path( - path_ctx @ PathCompletionCtx { kind: PathKind::Vis { has_in_token }, .. }, - ) = kind - { - completions::vis::complete_vis_path(&mut completions, ctx, path_ctx, has_in_token); - } + if let CompletionAnalysis::NameRef(NameRefContext { + kind: + NameRefKind::Path( + path_ctx @ PathCompletionCtx { kind: PathKind::Vis { has_in_token }, .. }, + ), + .. + }) = analysis + { + completions::vis::complete_vis_path(&mut completions, ctx, path_ctx, has_in_token); } return Some(completions.into()); } @@ -170,7 +172,7 @@ pub fn completions( { let acc = &mut completions; - match &analysis { + match analysis { CompletionAnalysis::Name(name_ctx) => completions::complete_name(acc, ctx, name_ctx), CompletionAnalysis::NameRef(name_ref_ctx) => { completions::complete_name_ref(acc, ctx, name_ref_ctx) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs index e48d1aecd..d99ad5f9f 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs @@ -503,18 +503,18 @@ mod tests { #[track_caller] fn check_relevance_for_kinds(ra_fixture: &str, kinds: &[CompletionItemKind], expect: Expect) { let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None); - actual.retain(|it| kinds.contains(&it.kind())); - actual.sort_by_key(|it| cmp::Reverse(it.relevance().score())); + actual.retain(|it| kinds.contains(&it.kind)); + actual.sort_by_key(|it| cmp::Reverse(it.relevance.score())); check_relevance_(actual, expect); } #[track_caller] fn check_relevance(ra_fixture: &str, expect: Expect) { let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None); - actual.retain(|it| it.kind() != CompletionItemKind::Snippet); - actual.retain(|it| it.kind() != CompletionItemKind::Keyword); - actual.retain(|it| it.kind() != CompletionItemKind::BuiltinType); - actual.sort_by_key(|it| cmp::Reverse(it.relevance().score())); + actual.retain(|it| it.kind != CompletionItemKind::Snippet); + actual.retain(|it| it.kind != CompletionItemKind::Keyword); + actual.retain(|it| it.kind != CompletionItemKind::BuiltinType); + actual.sort_by_key(|it| cmp::Reverse(it.relevance.score())); check_relevance_(actual, expect); } @@ -525,12 +525,11 @@ mod tests { .flat_map(|it| { let mut items = vec![]; - let tag = it.kind().tag(); - let relevance = display_relevance(it.relevance()); - items.push(format!("{tag} {} {relevance}\n", it.label())); + let tag = it.kind.tag(); + let relevance = display_relevance(it.relevance); + items.push(format!("{tag} {} {relevance}\n", it.label)); - if let Some((mutability, _offset, relevance)) = it.ref_match() { - let label = format!("&{}{}", mutability.as_keyword_for_ref(), it.label()); + if let Some((label, _indel, relevance)) = it.ref_match() { let relevance = display_relevance(relevance); items.push(format!("{tag} {label} {relevance}\n")); @@ -587,6 +586,7 @@ fn main() { Foo::Fo$0 } ), lookup: "Foo{}", detail: "Foo { x: i32, y: i32 }", + trigger_call_info: true, }, ] "#]], @@ -614,6 +614,7 @@ fn main() { Foo::Fo$0 } ), lookup: "Foo()", detail: "Foo(i32, i32)", + trigger_call_info: true, }, ] "#]], @@ -679,6 +680,7 @@ fn main() { Foo::Fo$0 } Variant, ), detail: "Foo", + trigger_call_info: true, }, ] "#]], @@ -745,6 +747,7 @@ fn main() { let _: m::Spam = S$0 } postfix_match: None, is_definite: false, }, + trigger_call_info: true, }, CompletionItem { label: "m::Spam::Foo", @@ -770,6 +773,7 @@ fn main() { let _: m::Spam = S$0 } postfix_match: None, is_definite: false, }, + trigger_call_info: true, }, ] "#]], @@ -942,6 +946,7 @@ use self::E::*; documentation: Documentation( "variant docs", ), + trigger_call_info: true, }, CompletionItem { label: "E", @@ -1691,6 +1696,7 @@ fn main() { sn while [] sn ref [] sn refm [] + sn unsafe [] sn match [] sn box [] sn dbg [] @@ -1718,6 +1724,7 @@ fn main() { me f() [] sn ref [] sn refm [] + sn unsafe [] sn match [] sn box [] sn dbg [] diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs index 64dab02f7..ed78fcd8e 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs @@ -113,7 +113,7 @@ fn render( item.detail(rendered.detail); match snippet_cap { - Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal), + Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal).trigger_call_info(), None => item.insert_text(rendered.literal), }; diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs index 1b09ad173..6e0c53ec9 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs @@ -72,7 +72,7 @@ pub(crate) fn render_union_literal( .set_relevance(ctx.completion_relevance()); match ctx.snippet_cap() { - Some(snippet_cap) => item.insert_snippet(snippet_cap, literal), + Some(snippet_cap) => item.insert_snippet(snippet_cap, literal).trigger_call_info(), None => item.insert_text(literal), }; diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs index abe14e48e..1fe48b9e9 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs @@ -23,7 +23,7 @@ mod type_pos; mod use_tree; mod visibility; -use hir::{db::DefDatabase, PrefixKind, Semantics}; +use hir::{db::DefDatabase, PrefixKind}; use ide_db::{ base_db::{fixture::ChangeFixture, FileLoader, FilePosition}, imports::insert_use::{ImportGranularity, InsertUseConfig}, @@ -31,7 +31,6 @@ use ide_db::{ }; use itertools::Itertools; use stdx::{format_to, trim_indent}; -use syntax::{AstNode, NodeOrToken, SyntaxElement}; use test_utils::assert_eq_text; use crate::{ @@ -75,6 +74,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { skip_glob_imports: true, }, snippets: Vec::new(), + limit: None, }; pub(crate) fn completion_list(ra_fixture: &str) -> String { @@ -108,10 +108,10 @@ fn completion_list_with_config( let items = get_all_items(config, ra_fixture, trigger_character); let items = items .into_iter() - .filter(|it| it.kind() != CompletionItemKind::BuiltinType || it.label() == "u32") - .filter(|it| include_keywords || it.kind() != CompletionItemKind::Keyword) - .filter(|it| include_keywords || it.kind() != CompletionItemKind::Snippet) - .sorted_by_key(|it| (it.kind(), it.label().to_owned(), it.detail().map(ToOwned::to_owned))) + .filter(|it| it.kind != CompletionItemKind::BuiltinType || it.label == "u32") + .filter(|it| include_keywords || it.kind != CompletionItemKind::Keyword) + .filter(|it| include_keywords || it.kind != CompletionItemKind::Snippet) + .sorted_by_key(|it| (it.kind, it.label.clone(), it.detail.as_ref().map(ToOwned::to_owned))) .collect(); render_completion_list(items) } @@ -138,8 +138,8 @@ pub(crate) fn do_completion_with_config( ) -> Vec<CompletionItem> { get_all_items(config, code, None) .into_iter() - .filter(|c| c.kind() == kind) - .sorted_by(|l, r| l.label().cmp(r.label())) + .filter(|c| c.kind == kind) + .sorted_by(|l, r| l.label.cmp(&r.label)) .collect() } @@ -148,18 +148,18 @@ fn render_completion_list(completions: Vec<CompletionItem>) -> String { s.chars().count() } let label_width = - completions.iter().map(|it| monospace_width(it.label())).max().unwrap_or_default().min(22); + completions.iter().map(|it| monospace_width(&it.label)).max().unwrap_or_default().min(22); completions .into_iter() .map(|it| { - let tag = it.kind().tag(); - let var_name = format!("{tag} {}", it.label()); + let tag = it.kind.tag(); + let var_name = format!("{tag} {}", it.label); let mut buf = var_name; - if let Some(detail) = it.detail() { - let width = label_width.saturating_sub(monospace_width(it.label())); + if let Some(detail) = it.detail { + let width = label_width.saturating_sub(monospace_width(&it.label)); format_to!(buf, "{:width$} {}", "", detail, width = width); } - if it.deprecated() { + if it.deprecated { format_to!(buf, " DEPRECATED"); } format_to!(buf, "\n"); @@ -191,13 +191,13 @@ pub(crate) fn check_edit_with_config( .unwrap_or_else(|| panic!("can't find {what:?} completion in {completions:#?}")); let mut actual = db.file_text(position.file_id).to_string(); - let mut combined_edit = completion.text_edit().to_owned(); + let mut combined_edit = completion.text_edit.clone(); resolve_completion_edits( &db, &config, position, - completion.imports_to_add().iter().filter_map(|import_edit| { + completion.import_to_add.iter().filter_map(|import_edit| { let import_path = &import_edit.import_path; let import_name = import_path.segments().last()?; Some((import_path.to_string(), import_name.to_string())) @@ -215,15 +215,6 @@ pub(crate) fn check_edit_with_config( assert_eq_text!(&ra_fixture_after, &actual) } -pub(crate) fn check_pattern_is_applicable(code: &str, check: impl FnOnce(SyntaxElement) -> bool) { - let (db, pos) = position(code); - - let sema = Semantics::new(&db); - let original_file = sema.parse(pos.file_id); - let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap(); - assert!(check(NodeOrToken::Token(token))); -} - pub(crate) fn get_all_items( config: CompletionConfig, code: &str, @@ -234,7 +225,7 @@ pub(crate) fn get_all_items( .map_or_else(Vec::default, Into::into); // validate res.iter().for_each(|it| { - let sr = it.source_range(); + let sr = it.source_range; assert!( sr.contains_inclusive(position.offset), "source range {sr:?} does not contain the offset {:?} of the completion request: {it:?}", @@ -245,8 +236,9 @@ pub(crate) fn get_all_items( } #[test] -fn test_no_completions_required() { +fn test_no_completions_in_for_loop_in_kw_pos() { assert_eq!(completion_list(r#"fn foo() { for i i$0 }"#), String::new()); + assert_eq!(completion_list(r#"fn foo() { for i in$0 }"#), String::new()); } #[test] diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs index 043f552bd..c1c6a689e 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs @@ -745,3 +745,255 @@ fn return_value_no_block() { r#"fn f() -> i32 { match () { () => return $0 } }"#, ); } + +#[test] +fn else_completion_after_if() { + check_empty( + r#" +fn foo() { if foo {} $0 } +"#, + expect![[r#" + fn foo() fn() + bt u32 + kw const + kw crate:: + kw else + kw else if + kw enum + kw extern + kw false + kw fn + kw for + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw self:: + kw static + kw struct + kw trait + kw true + kw type + kw union + kw unsafe + kw use + kw while + kw while let + sn macro_rules + sn pd + sn ppd + "#]], + ); + check_empty( + r#" +fn foo() { if foo {} el$0 } +"#, + expect![[r#" + fn foo() fn() + bt u32 + kw const + kw crate:: + kw else + kw else if + kw enum + kw extern + kw false + kw fn + kw for + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw self:: + kw static + kw struct + kw trait + kw true + kw type + kw union + kw unsafe + kw use + kw while + kw while let + sn macro_rules + sn pd + sn ppd + "#]], + ); + check_empty( + r#" +fn foo() { bar(if foo {} $0) } +"#, + expect![[r#" + fn foo() fn() + bt u32 + kw crate:: + kw else + kw else if + kw false + kw for + kw if + kw if let + kw loop + kw match + kw return + kw self:: + kw true + kw unsafe + kw while + kw while let + "#]], + ); + check_empty( + r#" +fn foo() { bar(if foo {} el$0) } +"#, + expect![[r#" + fn foo() fn() + bt u32 + kw crate:: + kw else + kw else if + kw false + kw for + kw if + kw if let + kw loop + kw match + kw return + kw self:: + kw true + kw unsafe + kw while + kw while let + "#]], + ); + check_empty( + r#" +fn foo() { if foo {} $0 let x = 92; } +"#, + expect![[r#" + fn foo() fn() + bt u32 + kw const + kw crate:: + kw else + kw else if + kw enum + kw extern + kw false + kw fn + kw for + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw self:: + kw static + kw struct + kw trait + kw true + kw type + kw union + kw unsafe + kw use + kw while + kw while let + sn macro_rules + sn pd + sn ppd + "#]], + ); + check_empty( + r#" +fn foo() { if foo {} el$0 let x = 92; } +"#, + expect![[r#" + fn foo() fn() + bt u32 + kw const + kw crate:: + kw else + kw else if + kw enum + kw extern + kw false + kw fn + kw for + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw self:: + kw static + kw struct + kw trait + kw true + kw type + kw union + kw unsafe + kw use + kw while + kw while let + sn macro_rules + sn pd + sn ppd + "#]], + ); + check_empty( + r#" +fn foo() { if foo {} el$0 { let x = 92; } } +"#, + expect![[r#" + fn foo() fn() + bt u32 + kw const + kw crate:: + kw else + kw else if + kw enum + kw extern + kw false + kw fn + kw for + kw if + kw if let + kw impl + kw let + kw loop + kw match + kw mod + kw return + kw self:: + kw static + kw struct + kw trait + kw true + kw type + kw union + kw unsafe + kw use + kw while + kw while let + sn macro_rules + sn pd + sn ppd + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs index a63ef0068..0b485eb77 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs @@ -541,9 +541,9 @@ fn main() { } "#, expect![[r#" - fn weird_function() (use dep::test_mod::TestTrait) fn() DEPRECATED - ct SPECIAL_CONST (use dep::test_mod::TestTrait) DEPRECATED - "#]], + ct SPECIAL_CONST (use dep::test_mod::TestTrait) DEPRECATED + fn weird_function() (use dep::test_mod::TestTrait) fn() DEPRECATED + "#]], ); } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs index b62b98888..9fc731bb1 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs @@ -215,6 +215,57 @@ fn in_trait_assoc_item_list() { } #[test] +fn in_trait_assoc_fn_missing_body() { + check( + r#"trait Foo { fn function(); $0 }"#, + expect![[r#" + ma makro!(…) macro_rules! makro + md module + kw const + kw crate:: + kw fn + kw self:: + kw type + kw unsafe + "#]], + ); +} + +#[test] +fn in_trait_assoc_const_missing_body() { + check( + r#"trait Foo { const CONST: (); $0 }"#, + expect![[r#" + ma makro!(…) macro_rules! makro + md module + kw const + kw crate:: + kw fn + kw self:: + kw type + kw unsafe + "#]], + ); +} + +#[test] +fn in_trait_assoc_type_aliases_missing_ty() { + check( + r#"trait Foo { type Type; $0 }"#, + expect![[r#" + ma makro!(…) macro_rules! makro + md module + kw const + kw crate:: + kw fn + kw self:: + kw type + kw unsafe + "#]], + ); +} + +#[test] fn in_trait_impl_assoc_item_list() { check( r#" diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs index 9eae6f849..92ea4d15b 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs @@ -24,16 +24,17 @@ fn main() { } "#, expect![[r#" - me foo() fn(&self) - sn box Box::new(expr) - sn call function(expr) - sn dbg dbg!(expr) - sn dbgr dbg!(&expr) - sn let let - sn letm let mut - sn match match expr {} - sn ref &expr - sn refm &mut expr + me foo() fn(&self) + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn unsafe unsafe {} "#]], ) } @@ -54,16 +55,17 @@ fn main() { } "#, expect![[r#" - me foo() fn(&self) - sn box Box::new(expr) - sn call function(expr) - sn dbg dbg!(expr) - sn dbgr dbg!(&expr) - sn let let - sn letm let mut - sn match match expr {} - sn ref &expr - sn refm &mut expr + me foo() fn(&self) + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn unsafe unsafe {} "#]], ) } @@ -86,16 +88,17 @@ impl Foo { fn main() {} "#, expect![[r#" - me foo() fn(&self) - sn box Box::new(expr) - sn call function(expr) - sn dbg dbg!(expr) - sn dbgr dbg!(&expr) - sn let let - sn letm let mut - sn match match expr {} - sn ref &expr - sn refm &mut expr + me foo() fn(&self) + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn unsafe unsafe {} "#]], ) } @@ -118,16 +121,47 @@ impl Foo { fn main() {} "#, expect![[r#" - me foo() fn(&self) - sn box Box::new(expr) - sn call function(expr) - sn dbg dbg!(expr) - sn dbgr dbg!(&expr) - sn let let - sn letm let mut - sn match match expr {} - sn ref &expr - sn refm &mut expr + me foo() fn(&self) + sn box Box::new(expr) + sn call function(expr) + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn unsafe unsafe {} "#]], ) } + +#[test] +fn issue_13836_str() { + check( + r#" +//- proc_macros: shorten +fn main() { + let s = proc_macros::shorten!("text.$0"); +} +"#, + expect![[r#""#]], + ) +} + +#[test] +fn issue_13836_ident() { + check( + r#" +//- proc_macros: shorten +struct S; +impl S { + fn foo(&self) {} +} +fn main() { + let s = proc_macros::shorten!(S.fo$0); +} +"#, + expect![[r#""#]], + ) +} diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs index cad4af493..cb71c7b2b 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs @@ -2,13 +2,20 @@ use expect_test::{expect, Expect}; -use crate::tests::{check_edit, completion_list_no_kw, completion_list_with_trigger_character}; +use crate::tests::{ + check_edit, completion_list, completion_list_no_kw, completion_list_with_trigger_character, +}; -fn check(ra_fixture: &str, expect: Expect) { +fn check_no_kw(ra_fixture: &str, expect: Expect) { let actual = completion_list_no_kw(ra_fixture); expect.assert_eq(&actual) } +fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list(ra_fixture); + expect.assert_eq(&actual) +} + pub(crate) fn check_with_trigger_character( ra_fixture: &str, trigger_character: Option<char>, @@ -59,9 +66,9 @@ fn _alpha() {} #[test] fn completes_prelude() { - check( + check_no_kw( r#" -//- /main.rs crate:main deps:std +//- /main.rs edition:2018 crate:main deps:std fn foo() { let x: $0 } //- /std/lib.rs crate:std @@ -81,9 +88,9 @@ pub mod prelude { #[test] fn completes_prelude_macros() { - check( + check_no_kw( r#" -//- /main.rs crate:main deps:std +//- /main.rs edition:2018 crate:main deps:std fn f() {$0} //- /std/lib.rs crate:std @@ -110,21 +117,21 @@ mod macros { #[test] fn completes_std_prelude_if_core_is_defined() { - check( + check_no_kw( r#" //- /main.rs crate:main deps:core,std fn foo() { let x: $0 } //- /core/lib.rs crate:core pub mod prelude { - pub mod rust_2018 { + pub mod rust_2021 { pub struct Option; } } //- /std/lib.rs crate:std deps:core pub mod prelude { - pub mod rust_2018 { + pub mod rust_2021 { pub struct String; } } @@ -140,7 +147,7 @@ pub mod prelude { #[test] fn respects_doc_hidden() { - check( + check_no_kw( r#" //- /lib.rs crate:lib deps:std fn f() { @@ -168,7 +175,7 @@ pub mod prelude { #[test] fn respects_doc_hidden_in_assoc_item_list() { - check( + check_no_kw( r#" //- /lib.rs crate:lib deps:std struct S; @@ -195,7 +202,7 @@ pub mod prelude { #[test] fn associated_item_visibility() { - check( + check_no_kw( r#" //- /lib.rs crate:lib new_source_root:library pub struct S; @@ -222,7 +229,7 @@ fn foo() { let _ = lib::S::$0 } #[test] fn completes_union_associated_method() { - check( + check_no_kw( r#" union U {}; impl U { fn m() { } } @@ -237,7 +244,7 @@ fn foo() { let _ = U::$0 } #[test] fn completes_trait_associated_method_1() { - check( + check_no_kw( r#" trait Trait { fn m(); } @@ -251,7 +258,7 @@ fn foo() { let _ = Trait::$0 } #[test] fn completes_trait_associated_method_2() { - check( + check_no_kw( r#" trait Trait { fn m(); } @@ -268,7 +275,7 @@ fn foo() { let _ = S::$0 } #[test] fn completes_trait_associated_method_3() { - check( + check_no_kw( r#" trait Trait { fn m(); } @@ -285,7 +292,7 @@ fn foo() { let _ = <S as Trait>::$0 } #[test] fn completes_ty_param_assoc_ty() { - check( + check_no_kw( r#" trait Super { type Ty; @@ -318,7 +325,7 @@ fn foo<T: Sub>() { T::$0 } #[test] fn completes_self_param_assoc_ty() { - check( + check_no_kw( r#" trait Super { type Ty; @@ -358,7 +365,7 @@ impl<T> Sub for Wrap<T> { #[test] fn completes_type_alias() { - check( + check_no_kw( r#" struct S; impl S { fn foo() {} } @@ -376,7 +383,7 @@ fn main() { T::$0; } #[test] fn completes_qualified_macros() { - check( + check_no_kw( r#" #[macro_export] macro_rules! foo { () => {} } @@ -392,7 +399,7 @@ fn main() { let _ = crate::$0 } #[test] fn does_not_complete_non_fn_macros() { - check( + check_no_kw( r#" mod m { #[rustc_builtin_macro] @@ -403,7 +410,7 @@ fn f() {m::$0} "#, expect![[r#""#]], ); - check( + check_no_kw( r#" mod m { #[rustc_builtin_macro] @@ -418,7 +425,7 @@ fn f() {m::$0} #[test] fn completes_reexported_items_under_correct_name() { - check( + check_no_kw( r#" fn foo() { self::m::$0 } @@ -475,7 +482,7 @@ mod p { #[test] fn completes_in_simple_macro_call() { - check( + check_no_kw( r#" macro_rules! m { ($e:expr) => { $e } } fn main() { m!(self::f$0); } @@ -490,7 +497,7 @@ fn foo() {} #[test] fn function_mod_share_name() { - check( + check_no_kw( r#" fn foo() { self::m::$0 } @@ -508,7 +515,7 @@ mod m { #[test] fn completes_hashmap_new() { - check( + check_no_kw( r#" struct RandomState; struct HashMap<K, V, S = RandomState> {} @@ -529,7 +536,7 @@ fn foo() { #[test] fn completes_variant_through_self() { cov_mark::check!(completes_variant_through_self); - check( + check_no_kw( r#" enum Foo { Bar, @@ -552,7 +559,7 @@ impl Foo { #[test] fn completes_non_exhaustive_variant_within_the_defining_crate() { - check( + check_no_kw( r#" enum Foo { #[non_exhaustive] @@ -570,7 +577,7 @@ fn foo(self) { "#]], ); - check( + check_no_kw( r#" //- /main.rs crate:main deps:e fn foo(self) { @@ -593,7 +600,7 @@ enum Foo { #[test] fn completes_primitive_assoc_const() { cov_mark::check!(completes_primitive_assoc_const); - check( + check_no_kw( r#" //- /lib.rs crate:lib deps:core fn f() { @@ -618,7 +625,7 @@ impl u8 { #[test] fn completes_variant_through_alias() { cov_mark::check!(completes_variant_through_alias); - check( + check_no_kw( r#" enum Foo { Bar @@ -636,7 +643,7 @@ fn main() { #[test] fn respects_doc_hidden2() { - check( + check_no_kw( r#" //- /lib.rs crate:lib deps:dep fn f() { @@ -665,7 +672,7 @@ pub mod m {} #[test] fn type_anchor_empty() { - check( + check_no_kw( r#" trait Foo { fn foo() -> Self; @@ -688,7 +695,7 @@ fn bar() -> Bar { #[test] fn type_anchor_type() { - check( + check_no_kw( r#" trait Foo { fn foo() -> Self; @@ -715,7 +722,7 @@ fn bar() -> Bar { #[test] fn type_anchor_type_trait() { - check( + check_no_kw( r#" trait Foo { fn foo() -> Self; @@ -741,7 +748,7 @@ fn bar() -> Bar { #[test] fn completes_fn_in_pub_trait_generated_by_macro() { - check( + check_no_kw( r#" mod other_mod { macro_rules! make_method { @@ -775,7 +782,7 @@ fn main() { #[test] fn completes_fn_in_pub_trait_generated_by_recursive_macro() { - check( + check_no_kw( r#" mod other_mod { macro_rules! make_method { @@ -815,7 +822,7 @@ fn main() { #[test] fn completes_const_in_pub_trait_generated_by_macro() { - check( + check_no_kw( r#" mod other_mod { macro_rules! make_const { @@ -847,7 +854,7 @@ fn main() { #[test] fn completes_locals_from_macros() { - check( + check_no_kw( r#" macro_rules! x { @@ -875,7 +882,7 @@ fn main() { #[test] fn regression_12644() { - check( + check_no_kw( r#" macro_rules! __rust_force_expr { ($e:expr) => { @@ -974,7 +981,7 @@ fn foo { crate:::$0 } "#, expect![""], ); - check( + check_no_kw( r#" fn foo { crate::::$0 } "#, diff --git a/src/tools/rust-analyzer/crates/ide-db/Cargo.toml b/src/tools/rust-analyzer/crates/ide-db/Cargo.toml index f48cce58c..57daaf623 100644 --- a/src/tools/rust-analyzer/crates/ide-db/Cargo.toml +++ b/src/tools/rust-analyzer/crates/ide-db/Cargo.toml @@ -2,9 +2,11 @@ name = "ide-db" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -12,29 +14,33 @@ doctest = false [dependencies] cov-mark = "2.0.0-pre.1" tracing = "0.1.35" -rayon = "1.5.3" +rayon = "1.6.1" fst = { version = "0.4.7", default-features = false } rustc-hash = "1.1.0" -once_cell = "1.15.0" +once_cell = "1.17.0" either = "1.7.0" itertools = "0.10.5" arrayvec = "0.7.2" indexmap = "1.9.1" memchr = "2.5.0" -stdx = { path = "../stdx", version = "0.0.0" } -parser = { path = "../parser", version = "0.0.0" } -syntax = { path = "../syntax", version = "0.0.0" } -text-edit = { path = "../text-edit", version = "0.0.0" } -base-db = { path = "../base-db", version = "0.0.0" } -profile = { path = "../profile", version = "0.0.0" } +# local deps +base-db.workspace = true +limit.workspace = true +parser.workspace = true +profile.workspace = true +stdx.workspace = true +syntax.workspace = true +text-edit.workspace = true # ide should depend only on the top-level `hir` package. if you need # something from some `hir-xxx` subpackage, reexport the API via `hir`. -hir = { path = "../hir", version = "0.0.0" } -limit = { path = "../limit", version = "0.0.0" } +hir.workspace = true [dev-dependencies] -test-utils = { path = "../test-utils" } -sourcegen = { path = "../sourcegen" } -xshell = "0.2.2" expect-test = "1.4.0" +oorandom = "11.1.3" +xshell = "0.2.2" + +# local deps +test-utils.workspace = true +sourcegen.workspace = true diff --git a/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs b/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs index 7109c6fd1..2b6b60547 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs @@ -2,9 +2,10 @@ use either::Either; use hir::{Semantics, Type}; +use parser::T; use syntax::{ ast::{self, HasArgList, HasName}, - AstNode, SyntaxToken, + match_ast, AstNode, NodeOrToken, SyntaxToken, }; use crate::RootDatabase; @@ -58,7 +59,7 @@ pub fn callable_for_node( calling_node: &ast::CallableExpr, token: &SyntaxToken, ) -> Option<(hir::Callable, Option<usize>)> { - let callable = match &calling_node { + let callable = match calling_node { ast::CallableExpr::Call(call) => { let expr = call.expr()?; sema.type_of_expr(&expr)?.adjusted().as_callable(sema.db) @@ -66,13 +67,78 @@ pub fn callable_for_node( ast::CallableExpr::MethodCall(call) => sema.resolve_method_call_as_callable(call), }?; let active_param = if let Some(arg_list) = calling_node.arg_list() { - let param = arg_list - .args() - .take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start()) - .count(); - Some(param) + Some( + arg_list + .syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .filter(|t| t.kind() == T![,]) + .take_while(|t| t.text_range().start() <= token.text_range().start()) + .count(), + ) } else { None }; Some((callable, active_param)) } + +pub fn generic_def_for_node( + sema: &Semantics<'_, RootDatabase>, + generic_arg_list: &ast::GenericArgList, + token: &SyntaxToken, +) -> Option<(hir::GenericDef, usize, bool)> { + let parent = generic_arg_list.syntax().parent()?; + let def = match_ast! { + match parent { + ast::PathSegment(ps) => { + let res = sema.resolve_path(&ps.parent_path())?; + let generic_def: hir::GenericDef = match res { + hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(), + hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(), + hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(), + hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(), + hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(), + hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_)) + | hir::PathResolution::Def(hir::ModuleDef::Const(_)) + | hir::PathResolution::Def(hir::ModuleDef::Macro(_)) + | hir::PathResolution::Def(hir::ModuleDef::Module(_)) + | hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None, + hir::PathResolution::BuiltinAttr(_) + | hir::PathResolution::ToolModule(_) + | hir::PathResolution::Local(_) + | hir::PathResolution::TypeParam(_) + | hir::PathResolution::ConstParam(_) + | hir::PathResolution::SelfType(_) + | hir::PathResolution::DeriveHelper(_) => return None, + }; + + generic_def + }, + ast::AssocTypeArg(_) => { + // FIXME: We don't record the resolutions for this anywhere atm + return None; + }, + ast::MethodCallExpr(mcall) => { + // recv.method::<$0>() + let method = sema.resolve_method_call(&mcall)?; + method.into() + }, + _ => return None, + } + }; + + let active_param = generic_arg_list + .syntax() + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .filter(|t| t.kind() == T![,]) + .take_while(|t| t.text_range().start() <= token.text_range().start()) + .count(); + + let first_arg_is_non_lifetime = generic_arg_list + .generic_args() + .next() + .map_or(false, |arg| !matches!(arg, ast::GenericArg::LifetimeArg(_))); + + Some((def, active_param, first_arg_is_non_lifetime)) +} diff --git a/src/tools/rust-analyzer/crates/ide-db/src/defs.rs b/src/tools/rust-analyzer/crates/ide-db/src/defs.rs index 6c13c0397..ed7f04fd8 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/defs.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/defs.rs @@ -34,8 +34,8 @@ pub enum Definition { TypeAlias(TypeAlias), BuiltinType(BuiltinType), SelfType(Impl), - Local(Local), GenericParam(GenericParam), + Local(Local), Label(Label), DeriveHelper(DeriveHelper), BuiltinAttr(BuiltinAttr), diff --git a/src/tools/rust-analyzer/crates/ide-db/src/line_index.rs b/src/tools/rust-analyzer/crates/ide-db/src/line_index.rs index 1b8f56187..16814a1e6 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/line_index.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/line_index.rs @@ -7,56 +7,72 @@ use syntax::{TextRange, TextSize}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct LineIndex { - /// Offset the the beginning of each line, zero-based + /// Offset the beginning of each line, zero-based. pub(crate) newlines: Vec<TextSize>, - /// List of non-ASCII characters on each line - pub(crate) utf16_lines: NoHashHashMap<u32, Vec<Utf16Char>>, + /// List of non-ASCII characters on each line. + pub(crate) line_wide_chars: NoHashHashMap<u32, Vec<WideChar>>, } +/// Line/Column information in native, utf8 format. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct LineColUtf16 { +pub struct LineCol { /// Zero-based pub line: u32, - /// Zero-based + /// Zero-based utf8 offset pub col: u32, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct LineCol { +pub enum WideEncoding { + Utf16, + Utf32, +} + +/// Line/Column information in legacy encodings. +/// +/// Deliberately not a generic type and different from `LineCol`. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct WideLineCol { /// Zero-based pub line: u32, - /// Zero-based utf8 offset + /// Zero-based pub col: u32, } #[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub(crate) struct Utf16Char { +pub(crate) struct WideChar { /// Start offset of a character inside a line, zero-based pub(crate) start: TextSize, /// End offset of a character inside a line, zero-based pub(crate) end: TextSize, } -impl Utf16Char { +impl WideChar { /// Returns the length in 8-bit UTF-8 code units. fn len(&self) -> TextSize { self.end - self.start } - /// Returns the length in 16-bit UTF-16 code units. - fn len_utf16(&self) -> usize { - if self.len() == TextSize::from(4) { - 2 - } else { - 1 + /// Returns the length in UTF-16 or UTF-32 code units. + fn wide_len(&self, enc: WideEncoding) -> usize { + match enc { + WideEncoding::Utf16 => { + if self.len() == TextSize::from(4) { + 2 + } else { + 1 + } + } + + WideEncoding::Utf32 => 1, } } } impl LineIndex { pub fn new(text: &str) -> LineIndex { - let mut utf16_lines = NoHashHashMap::default(); - let mut utf16_chars = Vec::new(); + let mut line_wide_chars = NoHashHashMap::default(); + let mut wide_chars = Vec::new(); let mut newlines = Vec::with_capacity(16); newlines.push(TextSize::from(0)); @@ -71,8 +87,8 @@ impl LineIndex { newlines.push(curr_row); // Save any utf-16 characters seen in the previous line - if !utf16_chars.is_empty() { - utf16_lines.insert(line, mem::take(&mut utf16_chars)); + if !wide_chars.is_empty() { + line_wide_chars.insert(line, mem::take(&mut wide_chars)); } // Prepare for processing the next line @@ -82,18 +98,18 @@ impl LineIndex { } if !c.is_ascii() { - utf16_chars.push(Utf16Char { start: curr_col, end: curr_col + c_len }); + wide_chars.push(WideChar { start: curr_col, end: curr_col + c_len }); } curr_col += c_len; } // Save any utf-16 characters seen in the last line - if !utf16_chars.is_empty() { - utf16_lines.insert(line, utf16_chars); + if !wide_chars.is_empty() { + line_wide_chars.insert(line, wide_chars); } - LineIndex { newlines, utf16_lines } + LineIndex { newlines, line_wide_chars } } pub fn line_col(&self, offset: TextSize) -> LineCol { @@ -109,13 +125,13 @@ impl LineIndex { .map(|offset| offset + TextSize::from(line_col.col)) } - pub fn to_utf16(&self, line_col: LineCol) -> LineColUtf16 { - let col = self.utf8_to_utf16_col(line_col.line, line_col.col.into()); - LineColUtf16 { line: line_col.line, col: col as u32 } + pub fn to_wide(&self, enc: WideEncoding, line_col: LineCol) -> WideLineCol { + let col = self.utf8_to_wide_col(enc, line_col.line, line_col.col.into()); + WideLineCol { line: line_col.line, col: col as u32 } } - pub fn to_utf8(&self, line_col: LineColUtf16) -> LineCol { - let col = self.utf16_to_utf8_col(line_col.line, line_col.col); + pub fn to_utf8(&self, enc: WideEncoding, line_col: WideLineCol) -> LineCol { + let col = self.wide_to_utf8_col(enc, line_col.line, line_col.col); LineCol { line: line_col.line, col: col.into() } } @@ -132,12 +148,12 @@ impl LineIndex { .filter(|it| !it.is_empty()) } - fn utf8_to_utf16_col(&self, line: u32, col: TextSize) -> usize { + fn utf8_to_wide_col(&self, enc: WideEncoding, line: u32, col: TextSize) -> usize { let mut res: usize = col.into(); - if let Some(utf16_chars) = self.utf16_lines.get(&line) { - for c in utf16_chars { + if let Some(wide_chars) = self.line_wide_chars.get(&line) { + for c in wide_chars { if c.end <= col { - res -= usize::from(c.len()) - c.len_utf16(); + res -= usize::from(c.len()) - c.wide_len(enc); } else { // From here on, all utf16 characters come *after* the character we are mapping, // so we don't need to take them into account @@ -148,11 +164,11 @@ impl LineIndex { res } - fn utf16_to_utf8_col(&self, line: u32, mut col: u32) -> TextSize { - if let Some(utf16_chars) = self.utf16_lines.get(&line) { - for c in utf16_chars { + fn wide_to_utf8_col(&self, enc: WideEncoding, line: u32, mut col: u32) -> TextSize { + if let Some(wide_chars) = self.line_wide_chars.get(&line) { + for c in wide_chars { if col > u32::from(c.start) { - col += u32::from(c.len()) - c.len_utf16() as u32; + col += u32::from(c.len()) - c.wide_len(enc) as u32; } else { // From here on, all utf16 characters come *after* the character we are mapping, // so we don't need to take them into account @@ -167,6 +183,9 @@ impl LineIndex { #[cfg(test)] mod tests { + use test_utils::skip_slow_tests; + + use super::WideEncoding::{Utf16, Utf32}; use super::*; #[test] @@ -185,14 +204,14 @@ mod tests { ]; let index = LineIndex::new(text); - for &(offset, line, col) in &table { + for (offset, line, col) in table { assert_eq!(index.line_col(offset.into()), LineCol { line, col }); } let text = "\nhello\nworld"; let table = [(0, 0, 0), (1, 1, 0), (2, 1, 1), (6, 1, 5), (7, 2, 0)]; let index = LineIndex::new(text); - for &(offset, line, col) in &table { + for (offset, line, col) in table { assert_eq!(index.line_col(offset.into()), LineCol { line, col }); } } @@ -210,67 +229,59 @@ mod tests { const C: char = 'x'; ", ); - assert_eq!(col_index.utf16_lines.len(), 0); + assert_eq!(col_index.line_wide_chars.len(), 0); } #[test] - fn test_single_char() { - let col_index = LineIndex::new( - " -const C: char = 'メ'; -", - ); - - assert_eq!(col_index.utf16_lines.len(), 1); - assert_eq!(col_index.utf16_lines[&1].len(), 1); - assert_eq!(col_index.utf16_lines[&1][0], Utf16Char { start: 17.into(), end: 20.into() }); - - // UTF-8 to UTF-16, no changes - assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15); - - // UTF-8 to UTF-16 - assert_eq!(col_index.utf8_to_utf16_col(1, 22.into()), 20); - - // UTF-16 to UTF-8, no changes - assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextSize::from(15)); - - // UTF-16 to UTF-8 - assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextSize::from(21)); - - let col_index = LineIndex::new("a𐐏b"); - assert_eq!(col_index.utf16_to_utf8_col(0, 3), TextSize::from(5)); - } - - #[test] - fn test_string() { - let col_index = LineIndex::new( - " -const C: char = \"メ メ\"; -", - ); - - assert_eq!(col_index.utf16_lines.len(), 1); - assert_eq!(col_index.utf16_lines[&1].len(), 2); - assert_eq!(col_index.utf16_lines[&1][0], Utf16Char { start: 17.into(), end: 20.into() }); - assert_eq!(col_index.utf16_lines[&1][1], Utf16Char { start: 21.into(), end: 24.into() }); - - // UTF-8 to UTF-16 - assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15); - - assert_eq!(col_index.utf8_to_utf16_col(1, 21.into()), 19); - assert_eq!(col_index.utf8_to_utf16_col(1, 25.into()), 21); - - assert!(col_index.utf8_to_utf16_col(2, 15.into()) == 15); - - // UTF-16 to UTF-8 - assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextSize::from(15)); + fn test_every_chars() { + if skip_slow_tests() { + return; + } - // メ UTF-8: 0xE3 0x83 0xA1, UTF-16: 0x30E1 - assert_eq!(col_index.utf16_to_utf8_col(1, 17), TextSize::from(17)); // first メ at 17..20 - assert_eq!(col_index.utf16_to_utf8_col(1, 18), TextSize::from(20)); // space - assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextSize::from(21)); // second メ at 21..24 + let text: String = { + let mut chars: Vec<char> = ((0 as char)..char::MAX).collect(); // Neat! + chars.extend("\n".repeat(chars.len() / 16).chars()); + let mut rng = oorandom::Rand32::new(stdx::rand::seed()); + stdx::rand::shuffle(&mut chars, |i| rng.rand_range(0..i as u32) as usize); + chars.into_iter().collect() + }; + assert!(text.contains('💩')); // Sanity check. + + let line_index = LineIndex::new(&text); + + let mut lin_col = LineCol { line: 0, col: 0 }; + let mut col_utf16 = 0; + let mut col_utf32 = 0; + for (offset, c) in text.char_indices() { + let got_offset = line_index.offset(lin_col).unwrap(); + assert_eq!(usize::from(got_offset), offset); + + let got_lin_col = line_index.line_col(got_offset); + assert_eq!(got_lin_col, lin_col); + + for enc in [Utf16, Utf32] { + let wide_lin_col = line_index.to_wide(enc, lin_col); + let got_lin_col = line_index.to_utf8(enc, wide_lin_col); + assert_eq!(got_lin_col, lin_col); + + let want_col = match enc { + Utf16 => col_utf16, + Utf32 => col_utf32, + }; + assert_eq!(wide_lin_col.col, want_col) + } - assert_eq!(col_index.utf16_to_utf8_col(2, 15), TextSize::from(15)); + if c == '\n' { + lin_col.line += 1; + lin_col.col = 0; + col_utf16 = 0; + col_utf32 = 0; + } else { + lin_col.col += c.len_utf8() as u32; + col_utf16 += c.len_utf16() as u32; + col_utf32 += 1; + } + } } #[test] diff --git a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs index 12d873b4a..6402a84a6 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs @@ -33,7 +33,7 @@ use syntax::{ /// } /// ``` pub struct PathTransform<'a> { - generic_def: hir::GenericDef, + generic_def: Option<hir::GenericDef>, substs: Vec<ast::Type>, target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>, @@ -49,7 +49,7 @@ impl<'a> PathTransform<'a> { PathTransform { source_scope, target_scope, - generic_def: trait_.into(), + generic_def: Some(trait_.into()), substs: get_syntactic_substs(impl_).unwrap_or_default(), } } @@ -63,28 +63,42 @@ impl<'a> PathTransform<'a> { PathTransform { source_scope, target_scope, - generic_def: function.into(), + generic_def: Some(function.into()), substs: get_type_args_from_arg_list(generic_arg_list).unwrap_or_default(), } } + pub fn generic_transformation( + target_scope: &'a SemanticsScope<'a>, + source_scope: &'a SemanticsScope<'a>, + ) -> PathTransform<'a> { + PathTransform { source_scope, target_scope, generic_def: None, substs: Vec::new() } + } + pub fn apply(&self, syntax: &SyntaxNode) { self.build_ctx().apply(syntax) } + pub fn apply_all<'b>(&self, nodes: impl IntoIterator<Item = &'b SyntaxNode>) { + let ctx = self.build_ctx(); + for node in nodes { + ctx.apply(node); + } + } + fn build_ctx(&self) -> Ctx<'a> { let db = self.source_scope.db; let target_module = self.target_scope.module(); let source_module = self.source_scope.module(); let skip = match self.generic_def { // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky - hir::GenericDef::Trait(_) => 1, + Some(hir::GenericDef::Trait(_)) => 1, _ => 0, }; let substs_by_param: FxHashMap<_, _> = self .generic_def - .type_params(db) .into_iter() + .flat_map(|it| it.type_params(db)) .skip(skip) // The actual list of trait type parameters may be longer than the one // used in the `impl` block due to trailing default type parameters. diff --git a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs index cd4a7e155..84d70b258 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs @@ -190,6 +190,7 @@ fn rename_mod( let InFile { file_id, value: def_source } = module.definition_source(sema.db); if let ModuleSource::SourceFile(..) = def_source { + let new_name = new_name.trim_start_matches("r#"); let anchor = file_id.original_file(sema.db); let is_mod_rs = module.is_mod_rs(sema.db); @@ -207,9 +208,13 @@ fn rename_mod( // - Module has submodules defined in separate files let dir_paths = match (is_mod_rs, has_detached_child, module.name(sema.db)) { // Go up one level since the anchor is inside the dir we're trying to rename - (true, _, Some(mod_name)) => Some((format!("../{mod_name}"), format!("../{new_name}"))), + (true, _, Some(mod_name)) => { + Some((format!("../{}", mod_name.unescaped()), format!("../{new_name}"))) + } // The anchor is on the same level as target dir - (false, true, Some(mod_name)) => Some((mod_name.to_string(), new_name.to_string())), + (false, true, Some(mod_name)) => { + Some((mod_name.unescaped().to_string(), new_name.to_string())) + } _ => None, }; @@ -263,11 +268,10 @@ fn rename_reference( Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) ) { match ident_kind { - IdentifierKind::Ident | IdentifierKind::Underscore => { - cov_mark::hit!(rename_not_a_lifetime_ident_ref); + IdentifierKind::Underscore => { bail!("Invalid name `{}`: not a lifetime identifier", new_name); } - IdentifierKind::Lifetime => cov_mark::hit!(rename_lifetime), + _ => cov_mark::hit!(rename_lifetime), } } else { match ident_kind { @@ -334,11 +338,17 @@ pub fn source_edit_from_references( } _ => false, }; - if !has_emitted_edit { - if !edited_ranges.contains(&range.start()) { - edit.replace(range, new_name.to_string()); - edited_ranges.push(range.start()); - } + if !has_emitted_edit && !edited_ranges.contains(&range.start()) { + let (range, new_name) = match name { + ast::NameLike::Lifetime(_) => ( + TextRange::new(range.start() + syntax::TextSize::from(1), range.end()), + new_name.strip_prefix('\'').unwrap_or(new_name).to_owned(), + ), + _ => (range, new_name.to_owned()), + }; + + edit.replace(range, new_name); + edited_ranges.push(range.start()); } } @@ -391,19 +401,17 @@ fn source_edit_from_name_ref( edit.delete(TextRange::new(s, e)); return true; } - } else if init == name_ref { - if field_name.text() == new_name { - cov_mark::hit!(test_rename_local_put_init_shorthand); - // Foo { field: local } -> Foo { field } - // ^^^^^^^ delete this - - // same names, we can use a shorthand here instead. - // we do not want to erase attributes hence this range start - let s = field_name.syntax().text_range().end(); - let e = init.syntax().text_range().end(); - edit.delete(TextRange::new(s, e)); - return true; - } + } else if init == name_ref && field_name.text() == new_name { + cov_mark::hit!(test_rename_local_put_init_shorthand); + // Foo { field: local } -> Foo { field } + // ^^^^^^^ delete this + + // same names, we can use a shorthand here instead. + // we do not want to erase attributes hence this range start + let s = field_name.syntax().text_range().end(); + let e = init.syntax().text_range().end(); + edit.delete(TextRange::new(s, e)); + return true; } } // init shorthand @@ -505,7 +513,15 @@ fn source_edit_from_def( } } if edit.is_empty() { - edit.replace(range, new_name.to_string()); + let (range, new_name) = match def { + Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) + | Definition::Label(_) => ( + TextRange::new(range.start() + syntax::TextSize::from(1), range.end()), + new_name.strip_prefix('\'').unwrap_or(new_name).to_owned(), + ), + _ => (range, new_name.to_owned()), + }; + edit.replace(range, new_name); } Ok((file_id, edit.finish())) } @@ -521,14 +537,18 @@ impl IdentifierKind { pub fn classify(new_name: &str) -> Result<IdentifierKind> { match parser::LexedStr::single_token(new_name) { Some(res) => match res { - (SyntaxKind::IDENT, _) => Ok(IdentifierKind::Ident), + (SyntaxKind::IDENT, _) => { + if let Some(inner) = new_name.strip_prefix("r#") { + if matches!(inner, "self" | "crate" | "super" | "Self") { + bail!("Invalid name: `{}` cannot be a raw identifier", inner); + } + } + Ok(IdentifierKind::Ident) + } (T![_], _) => Ok(IdentifierKind::Underscore), (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => { Ok(IdentifierKind::Lifetime) } - (SyntaxKind::LIFETIME_IDENT, _) => { - bail!("Invalid name `{}`: not a lifetime identifier", new_name) - } (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), }, diff --git a/src/tools/rust-analyzer/crates/ide-db/src/search.rs b/src/tools/rust-analyzer/crates/ide-db/src/search.rs index b2b0e4908..c18a27f17 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs @@ -7,7 +7,9 @@ use std::{mem, sync::Arc}; use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt}; -use hir::{DefWithBody, HasAttrs, HasSource, InFile, ModuleSource, Semantics, Visibility}; +use hir::{ + AsAssocItem, DefWithBody, HasAttrs, HasSource, InFile, ModuleSource, Semantics, Visibility, +}; use memchr::memmem::Finder; use once_cell::unsync::Lazy; use parser::SyntaxKind; @@ -311,15 +313,15 @@ impl Definition { pub fn usages<'a>(self, sema: &'a Semantics<'_, RootDatabase>) -> FindUsages<'a> { FindUsages { - local_repr: match self { - Definition::Local(local) => Some(local.representative(sema.db)), - _ => None, - }, def: self, - trait_assoc_def: as_trait_assoc_def(sema.db, self), + assoc_item_container: self.as_assoc_item(sema.db).map(|a| a.container(sema.db)), sema, scope: None, include_self_kw_refs: None, + local_repr: match self { + Definition::Local(local) => Some(local.representative(sema.db)), + _ => None, + }, search_self_mod: false, } } @@ -328,12 +330,16 @@ impl Definition { #[derive(Clone)] pub struct FindUsages<'a> { def: Definition, - /// If def is an assoc item from a trait or trait impl, this is the corresponding item of the trait definition - trait_assoc_def: Option<Definition>, sema: &'a Semantics<'a, RootDatabase>, scope: Option<SearchScope>, + /// The container of our definition should it be an assoc item + assoc_item_container: Option<hir::AssocItemContainer>, + /// whether to search for the `Self` type of the definition include_self_kw_refs: Option<hir::Type>, + /// the local representative for the local definition we are searching for + /// (this is required for finding all local declarations in a or-pattern) local_repr: Option<hir::Local>, + /// whether to search for the `self` module search_self_mod: bool, } @@ -380,7 +386,9 @@ impl<'a> FindUsages<'a> { let sema = self.sema; let search_scope = { - let base = self.trait_assoc_def.unwrap_or(self.def).search_scope(sema.db); + // FIXME: Is the trait scope needed for trait impl assoc items? + let base = + as_trait_assoc_def(sema.db, self.def).unwrap_or(self.def).search_scope(sema.db); match &self.scope { None => base, Some(scope) => base.intersection(scope), @@ -447,15 +455,21 @@ impl<'a> FindUsages<'a> { } let find_nodes = move |name: &str, node: &syntax::SyntaxNode, offset: TextSize| { - node.token_at_offset(offset).find(|it| it.text() == name).map(|token| { - // FIXME: There should be optimization potential here - // Currently we try to descend everything we find which - // means we call `Semantics::descend_into_macros` on - // every textual hit. That function is notoriously - // expensive even for things that do not get down mapped - // into macros. - sema.descend_into_macros(token).into_iter().filter_map(|it| it.parent()) - }) + node.token_at_offset(offset) + .find(|it| { + // `name` is stripped of raw ident prefix. See the comment on name retrieval above. + it.text().trim_start_matches("r#") == name + }) + .into_iter() + .flat_map(|token| { + // FIXME: There should be optimization potential here + // Currently we try to descend everything we find which + // means we call `Semantics::descend_into_macros` on + // every textual hit. That function is notoriously + // expensive even for things that do not get down mapped + // into macros. + sema.descend_into_macros(token).into_iter().filter_map(|it| it.parent()) + }) }; for (text, file_id, search_range) in scope_files(sema, &search_scope) { @@ -463,30 +477,23 @@ impl<'a> FindUsages<'a> { // Search for occurrences of the items name for offset in match_indices(&text, finder, search_range) { - if let Some(iter) = find_nodes(name, &tree, offset) { - for name in iter.filter_map(ast::NameLike::cast) { - if match name { - ast::NameLike::NameRef(name_ref) => { - self.found_name_ref(&name_ref, sink) - } - ast::NameLike::Name(name) => self.found_name(&name, sink), - ast::NameLike::Lifetime(lifetime) => { - self.found_lifetime(&lifetime, sink) - } - } { - return; - } + for name in find_nodes(name, &tree, offset).filter_map(ast::NameLike::cast) { + if match name { + ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink), + ast::NameLike::Name(name) => self.found_name(&name, sink), + ast::NameLike::Lifetime(lifetime) => self.found_lifetime(&lifetime, sink), + } { + return; } } } // Search for occurrences of the `Self` referring to our type if let Some((self_ty, finder)) = &include_self_kw_refs { for offset in match_indices(&text, finder, search_range) { - if let Some(iter) = find_nodes("Self", &tree, offset) { - for name_ref in iter.filter_map(ast::NameRef::cast) { - if self.found_self_ty_name_ref(self_ty, &name_ref, sink) { - return; - } + for name_ref in find_nodes("Self", &tree, offset).filter_map(ast::NameRef::cast) + { + if self.found_self_ty_name_ref(self_ty, &name_ref, sink) { + return; } } } @@ -494,41 +501,37 @@ impl<'a> FindUsages<'a> { } // Search for `super` and `crate` resolving to our module - match self.def { - Definition::Module(module) => { - let scope = search_scope - .intersection(&SearchScope::module_and_children(self.sema.db, module)); + if let Definition::Module(module) = self.def { + let scope = + search_scope.intersection(&SearchScope::module_and_children(self.sema.db, module)); - let is_crate_root = - module.is_crate_root(self.sema.db).then(|| Finder::new("crate")); - let finder = &Finder::new("super"); + let is_crate_root = module.is_crate_root(self.sema.db).then(|| Finder::new("crate")); + let finder = &Finder::new("super"); - for (text, file_id, search_range) in scope_files(sema, &scope) { - let tree = Lazy::new(move || sema.parse(file_id).syntax().clone()); + for (text, file_id, search_range) in scope_files(sema, &scope) { + let tree = Lazy::new(move || sema.parse(file_id).syntax().clone()); - for offset in match_indices(&text, finder, search_range) { - if let Some(iter) = find_nodes("super", &tree, offset) { - for name_ref in iter.filter_map(ast::NameRef::cast) { - if self.found_name_ref(&name_ref, sink) { - return; - } - } + for offset in match_indices(&text, finder, search_range) { + for name_ref in + find_nodes("super", &tree, offset).filter_map(ast::NameRef::cast) + { + if self.found_name_ref(&name_ref, sink) { + return; } } - if let Some(finder) = &is_crate_root { - for offset in match_indices(&text, finder, search_range) { - if let Some(iter) = find_nodes("crate", &tree, offset) { - for name_ref in iter.filter_map(ast::NameRef::cast) { - if self.found_name_ref(&name_ref, sink) { - return; - } - } + } + if let Some(finder) = &is_crate_root { + for offset in match_indices(&text, finder, search_range) { + for name_ref in + find_nodes("crate", &tree, offset).filter_map(ast::NameRef::cast) + { + if self.found_name_ref(&name_ref, sink) { + return; } } } } } - _ => (), } // search for module `self` references in our module's definition source @@ -562,11 +565,10 @@ impl<'a> FindUsages<'a> { let finder = &Finder::new("self"); for offset in match_indices(&text, finder, search_range) { - if let Some(iter) = find_nodes("self", &tree, offset) { - for name_ref in iter.filter_map(ast::NameRef::cast) { - if self.found_self_module_name_ref(&name_ref, sink) { - return; - } + for name_ref in find_nodes("self", &tree, offset).filter_map(ast::NameRef::cast) + { + if self.found_self_module_name_ref(&name_ref, sink) { + return; } } } @@ -655,13 +657,26 @@ impl<'a> FindUsages<'a> { sink(file_id, reference) } Some(NameRefClass::Definition(def)) - if match self.trait_assoc_def { - Some(trait_assoc_def) => { - // we have a trait assoc item, so force resolve all assoc items to their trait version - convert_to_def_in_trait(self.sema.db, def) == trait_assoc_def - } - None => self.def == def, - } => + if self.def == def + // is our def a trait assoc item? then we want to find all assoc items from trait impls of our trait + || matches!(self.assoc_item_container, Some(hir::AssocItemContainer::Trait(_))) + && convert_to_def_in_trait(self.sema.db, def) == self.def => + { + let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); + let reference = FileReference { + range, + name: ast::NameLike::NameRef(name_ref.clone()), + category: ReferenceCategory::new(&def, name_ref), + }; + sink(file_id, reference) + } + // FIXME: special case type aliases, we can't filter between impl and trait defs here as we lack the substitutions + // so we always resolve all assoc type aliases to both their trait def and impl defs + Some(NameRefClass::Definition(def)) + if self.assoc_item_container.is_some() + && matches!(self.def, Definition::TypeAlias(_)) + && convert_to_def_in_trait(self.sema.db, def) + == convert_to_def_in_trait(self.sema.db, self.def) => { let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); let reference = FileReference { @@ -752,13 +767,21 @@ impl<'a> FindUsages<'a> { false } Some(NameClass::Definition(def)) if def != self.def => { - // if the def we are looking for is a trait (impl) assoc item, we'll have to resolve the items to trait definition assoc item - if !matches!( - self.trait_assoc_def, - Some(trait_assoc_def) - if convert_to_def_in_trait(self.sema.db, def) == trait_assoc_def - ) { - return false; + match (&self.assoc_item_container, self.def) { + // for type aliases we always want to reference the trait def and all the trait impl counterparts + // FIXME: only until we can resolve them correctly, see FIXME above + (Some(_), Definition::TypeAlias(_)) + if convert_to_def_in_trait(self.sema.db, def) + != convert_to_def_in_trait(self.sema.db, self.def) => + { + return false + } + (Some(_), Definition::TypeAlias(_)) => {} + // We looking at an assoc item of a trait definition, so reference all the + // corresponding assoc items belonging to this trait's trait implementations + (Some(hir::AssocItemContainer::Trait(_)), _) + if convert_to_def_in_trait(self.sema.db, def) == self.def => {} + _ => return false, } let FileRange { file_id, range } = self.sema.original_range(name.syntax()); let reference = FileReference { diff --git a/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs b/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs index c054cc159..a91ffd1ec 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs @@ -323,10 +323,10 @@ impl Query { if symbol.name != self.query { continue; } - } else if self.case_sensitive { - if self.query.chars().any(|c| !symbol.name.contains(c)) { - continue; - } + } else if self.case_sensitive + && self.query.chars().any(|c| !symbol.name.contains(c)) + { + continue; } res.push(symbol.clone()); diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml b/src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml index 7e9a1125d..e18624fcc 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml @@ -2,9 +2,11 @@ name = "ide-diagnostics" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -15,19 +17,21 @@ either = "1.7.0" itertools = "0.10.5" serde_json = "1.0.86" -profile = { path = "../profile", version = "0.0.0" } -stdx = { path = "../stdx", version = "0.0.0" } -syntax = { path = "../syntax", version = "0.0.0" } -text-edit = { path = "../text-edit", version = "0.0.0" } -cfg = { path = "../cfg", version = "0.0.0" } -hir = { path = "../hir", version = "0.0.0" } -ide-db = { path = "../ide-db", version = "0.0.0" } +# local deps +profile.workspace = true +stdx.workspace = true +syntax.workspace = true +text-edit.workspace = true +cfg.workspace = true +hir.workspace = true +ide-db.workspace = true [dev-dependencies] expect-test = "1.4.0" -test-utils = { path = "../test-utils" } -sourcegen = { path = "../sourcegen" } +# local deps +test-utils.workspace = true +sourcegen.workspace = true [features] in-rust-tree = [] diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs index e8df6dcf2..04ce1e0fe 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs @@ -99,76 +99,66 @@ pub(crate) fn json_in_items( && node.last_token().map(|x| x.kind()) == Some(SyntaxKind::R_CURLY) { let node_string = node.to_string(); - if let Ok(it) = serde_json::from_str(&node_string) { - if let serde_json::Value::Object(it) = it { - let import_scope = ImportScope::find_insert_use_container(node, sema)?; - let range = node.text_range(); - let mut edit = TextEdit::builder(); - edit.delete(range); - let mut state = State::default(); - let semantics_scope = sema.scope(node)?; - let scope_resolve = - |it| semantics_scope.speculative_resolve(&make::path_from_text(it)); - let scope_has = |it| scope_resolve(it).is_some(); - let deserialize_resolved = scope_resolve("::serde::Deserialize"); - let serialize_resolved = scope_resolve("::serde::Serialize"); - state.has_deserialize = deserialize_resolved.is_some(); - state.has_serialize = serialize_resolved.is_some(); - state.build_struct(&it); - edit.insert(range.start(), state.result); - acc.push( - Diagnostic::new( - "json-is-not-rust", - "JSON syntax is not valid as a Rust item", - range, - ) - .severity(Severity::WeakWarning) - .with_fixes(Some(vec![{ - let mut scb = SourceChangeBuilder::new(file_id); - let scope = match import_scope { - ImportScope::File(it) => ImportScope::File(scb.make_mut(it)), - ImportScope::Module(it) => ImportScope::Module(scb.make_mut(it)), - ImportScope::Block(it) => ImportScope::Block(scb.make_mut(it)), - }; - let current_module = semantics_scope.module(); - if !scope_has("Serialize") { - if let Some(PathResolution::Def(it)) = serialize_resolved { - if let Some(it) = current_module.find_use_path_prefixed( - sema.db, - it, - config.insert_use.prefix_kind, - config.prefer_no_std, - ) { - insert_use( - &scope, - mod_path_to_ast(&it), - &config.insert_use, - ); - } + if let Ok(serde_json::Value::Object(it)) = serde_json::from_str(&node_string) { + let import_scope = ImportScope::find_insert_use_container(node, sema)?; + let range = node.text_range(); + let mut edit = TextEdit::builder(); + edit.delete(range); + let mut state = State::default(); + let semantics_scope = sema.scope(node)?; + let scope_resolve = + |it| semantics_scope.speculative_resolve(&make::path_from_text(it)); + let scope_has = |it| scope_resolve(it).is_some(); + let deserialize_resolved = scope_resolve("::serde::Deserialize"); + let serialize_resolved = scope_resolve("::serde::Serialize"); + state.has_deserialize = deserialize_resolved.is_some(); + state.has_serialize = serialize_resolved.is_some(); + state.build_struct(&it); + edit.insert(range.start(), state.result); + acc.push( + Diagnostic::new( + "json-is-not-rust", + "JSON syntax is not valid as a Rust item", + range, + ) + .severity(Severity::WeakWarning) + .with_fixes(Some(vec![{ + let mut scb = SourceChangeBuilder::new(file_id); + let scope = match import_scope { + ImportScope::File(it) => ImportScope::File(scb.make_mut(it)), + ImportScope::Module(it) => ImportScope::Module(scb.make_mut(it)), + ImportScope::Block(it) => ImportScope::Block(scb.make_mut(it)), + }; + let current_module = semantics_scope.module(); + if !scope_has("Serialize") { + if let Some(PathResolution::Def(it)) = serialize_resolved { + if let Some(it) = current_module.find_use_path_prefixed( + sema.db, + it, + config.insert_use.prefix_kind, + config.prefer_no_std, + ) { + insert_use(&scope, mod_path_to_ast(&it), &config.insert_use); } } - if !scope_has("Deserialize") { - if let Some(PathResolution::Def(it)) = deserialize_resolved { - if let Some(it) = current_module.find_use_path_prefixed( - sema.db, - it, - config.insert_use.prefix_kind, - config.prefer_no_std, - ) { - insert_use( - &scope, - mod_path_to_ast(&it), - &config.insert_use, - ); - } + } + if !scope_has("Deserialize") { + if let Some(PathResolution::Def(it)) = deserialize_resolved { + if let Some(it) = current_module.find_use_path_prefixed( + sema.db, + it, + config.insert_use.prefix_kind, + config.prefer_no_std, + ) { + insert_use(&scope, mod_path_to_ast(&it), &config.insert_use); } } - let mut sc = scb.finish(); - sc.insert_source_edit(file_id, edit.finish()); - fix("convert_json_to_struct", "Convert JSON to struct", sc, range) - }])), - ); - } + } + let mut sc = scb.finish(); + sc.insert_source_edit(file_id, edit.finish()); + fix("convert_json_to_struct", "Convert JSON to struct", sc, range) + }])), + ); } } Some(()) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs index b363a516d..0b3121c76 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs @@ -11,10 +11,7 @@ pub(crate) fn private_assoc_item( d: &hir::PrivateAssocItem, ) -> Diagnostic { // FIXME: add quickfix - let name = match d.item.name(ctx.sema.db) { - Some(name) => format!("`{}` ", name), - None => String::new(), - }; + let name = d.item.name(ctx.sema.db).map(|name| format!("`{name}` ")).unwrap_or_default(); Diagnostic::new( "private-assoc-item", format!( diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs index be70f0ac4..3d45a7591 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs @@ -1,13 +1,15 @@ //! Diagnostic emitted for files that aren't part of any crate. -use hir::db::DefDatabase; +use std::iter; + +use hir::{db::DefDatabase, InFile, ModuleSource}; use ide_db::{ base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt}, source_change::SourceChange, RootDatabase, }; use syntax::{ - ast::{self, HasModuleItem, HasName}, + ast::{self, edit::IndentLevel, HasModuleItem, HasName}, AstNode, TextRange, TextSize, }; use text_edit::TextEdit; @@ -42,47 +44,99 @@ fn fixes(ctx: &DiagnosticsContext<'_>, file_id: FileId) -> Option<Vec<Assist>> { let source_root = ctx.sema.db.source_root(ctx.sema.db.file_source_root(file_id)); let our_path = source_root.path_for_file(&file_id)?; - let (mut module_name, _) = our_path.name_and_extension()?; - - // Candidates to look for: - // - `mod.rs`, `main.rs` and `lib.rs` in the same folder - // - `$dir.rs` in the parent folder, where `$dir` is the directory containing `self.file_id` let parent = our_path.parent()?; - let paths = { - let parent = if module_name == "mod" { - // for mod.rs we need to actually look up one higher - // and take the parent as our to be module name - let (name, _) = parent.name_and_extension()?; - module_name = name; - parent.parent()? - } else { - parent - }; - let mut paths = - vec![parent.join("mod.rs")?, parent.join("lib.rs")?, parent.join("main.rs")?]; - - // `submod/bla.rs` -> `submod.rs` - let parent_mod = (|| { + let (module_name, _) = our_path.name_and_extension()?; + let (parent, module_name) = match module_name { + // for mod.rs we need to actually look up one higher + // and take the parent as our to be module name + "mod" => { let (name, _) = parent.name_and_extension()?; - parent.parent()?.join(&format!("{name}.rs")) - })(); - paths.extend(parent_mod); - paths + (parent.parent()?, name.to_owned()) + } + _ => (parent, module_name.to_owned()), }; - for &parent_id in paths.iter().filter_map(|path| source_root.file_for_path(path)) { - for &krate in ctx.sema.db.relevant_crates(parent_id).iter() { - let crate_def_map = ctx.sema.db.crate_def_map(krate); - for (_, module) in crate_def_map.modules() { - if module.origin.is_inline() { - // We don't handle inline `mod parent {}`s, they use different paths. - continue; - } + // check crate roots, i.e. main.rs, lib.rs, ... + 'crates: for &krate in &*ctx.sema.db.relevant_crates(file_id) { + let crate_def_map = ctx.sema.db.crate_def_map(krate); + + let root_module = &crate_def_map[crate_def_map.root()]; + let Some(root_file_id) = root_module.origin.file_id() else { continue }; + let Some(crate_root_path) = source_root.path_for_file(&root_file_id) else { continue }; + let Some(rel) = parent.strip_prefix(&crate_root_path.parent()?) else { continue }; + + // try resolving the relative difference of the paths as inline modules + let mut current = root_module; + for ele in rel.as_ref().components() { + let seg = match ele { + std::path::Component::Normal(seg) => seg.to_str()?, + std::path::Component::RootDir => continue, + // shouldn't occur + _ => continue 'crates, + }; + match current.children.iter().find(|(name, _)| name.to_smol_str() == seg) { + Some((_, &child)) => current = &crate_def_map[child], + None => continue 'crates, + } + if !current.origin.is_inline() { + continue 'crates; + } + } + + let InFile { file_id: parent_file_id, value: source } = + current.definition_source(ctx.sema.db); + let parent_file_id = parent_file_id.file_id()?; + return make_fixes(ctx.sema.db, parent_file_id, source, &module_name, file_id); + } - if module.origin.file_id() == Some(parent_id) { - return make_fixes(ctx.sema.db, parent_id, module_name, file_id); + // if we aren't adding to a crate root, walk backwards such that we support `#[path = ...]` overrides if possible + + // build all parent paths of the form `../module_name/mod.rs` and `../module_name.rs` + let paths = iter::successors(Some(parent.clone()), |prev| prev.parent()).filter_map(|path| { + let parent = path.parent()?; + let (name, _) = path.name_and_extension()?; + Some(([parent.join(&format!("{name}.rs"))?, path.join("mod.rs")?], name.to_owned())) + }); + let mut stack = vec![]; + let &parent_id = + paths.inspect(|(_, name)| stack.push(name.clone())).find_map(|(paths, _)| { + paths.into_iter().find_map(|path| source_root.file_for_path(&path)) + })?; + stack.pop(); + 'crates: for &krate in ctx.sema.db.relevant_crates(parent_id).iter() { + let crate_def_map = ctx.sema.db.crate_def_map(krate); + let Some((_, module)) = + crate_def_map.modules() + .find(|(_, module)| module.origin.file_id() == Some(parent_id) && !module.origin.is_inline()) + else { continue }; + + if stack.is_empty() { + return make_fixes( + ctx.sema.db, + parent_id, + module.definition_source(ctx.sema.db).value, + &module_name, + file_id, + ); + } else { + // direct parent file is missing, + // try finding a parent that has an inline tree from here on + let mut current = module; + for s in stack.iter().rev() { + match module.children.iter().find(|(name, _)| name.to_smol_str() == s) { + Some((_, child)) => { + current = &crate_def_map[*child]; + } + None => continue 'crates, + } + if !current.origin.is_inline() { + continue 'crates; } } + let InFile { file_id: parent_file_id, value: source } = + current.definition_source(ctx.sema.db); + let parent_file_id = parent_file_id.file_id()?; + return make_fixes(ctx.sema.db, parent_file_id, source, &module_name, file_id); } } @@ -92,6 +146,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, file_id: FileId) -> Option<Vec<Assist>> { fn make_fixes( db: &RootDatabase, parent_file_id: FileId, + source: ModuleSource, new_mod_name: &str, added_file_id: FileId, ) -> Option<Vec<Assist>> { @@ -102,14 +157,18 @@ fn make_fixes( let mod_decl = format!("mod {new_mod_name};"); let pub_mod_decl = format!("pub mod {new_mod_name};"); - let ast: ast::SourceFile = db.parse(parent_file_id).tree(); - let mut mod_decl_builder = TextEdit::builder(); let mut pub_mod_decl_builder = TextEdit::builder(); + let mut items = match &source { + ModuleSource::SourceFile(it) => it.items(), + ModuleSource::Module(it) => it.item_list()?.items(), + ModuleSource::BlockExpr(_) => return None, + }; + // If there's an existing `mod m;` statement matching the new one, don't emit a fix (it's // probably `#[cfg]`d out). - for item in ast.items() { + for item in items.clone() { if let ast::Item::Module(m) = item { if let Some(name) = m.name() { if m.item_list().is_none() && name.to_string() == new_mod_name { @@ -121,28 +180,40 @@ fn make_fixes( } // If there are existing `mod m;` items, append after them (after the first group of them, rather). - match ast.items().skip_while(|item| !is_outline_mod(item)).take_while(is_outline_mod).last() { + match items.clone().skip_while(|item| !is_outline_mod(item)).take_while(is_outline_mod).last() { Some(last) => { cov_mark::hit!(unlinked_file_append_to_existing_mods); let offset = last.syntax().text_range().end(); - mod_decl_builder.insert(offset, format!("\n{mod_decl}")); - pub_mod_decl_builder.insert(offset, format!("\n{pub_mod_decl}")); + let indent = IndentLevel::from_node(last.syntax()); + mod_decl_builder.insert(offset, format!("\n{indent}{mod_decl}")); + pub_mod_decl_builder.insert(offset, format!("\n{indent}{pub_mod_decl}")); } None => { // Prepend before the first item in the file. - match ast.items().next() { - Some(item) => { + match items.next() { + Some(first) => { cov_mark::hit!(unlinked_file_prepend_before_first_item); - let offset = item.syntax().text_range().start(); - mod_decl_builder.insert(offset, format!("{mod_decl}\n\n")); - pub_mod_decl_builder.insert(offset, format!("{pub_mod_decl}\n\n")); + let offset = first.syntax().text_range().start(); + let indent = IndentLevel::from_node(first.syntax()); + mod_decl_builder.insert(offset, format!("{mod_decl}\n\n{indent}")); + pub_mod_decl_builder.insert(offset, format!("{pub_mod_decl}\n\n{indent}")); } None => { // No items in the file, so just append at the end. cov_mark::hit!(unlinked_file_empty_file); - let offset = ast.syntax().text_range().end(); - mod_decl_builder.insert(offset, format!("{mod_decl}\n")); - pub_mod_decl_builder.insert(offset, format!("{pub_mod_decl}\n")); + let mut indent = IndentLevel::from(0); + let offset = match &source { + ModuleSource::SourceFile(it) => it.syntax().text_range().end(), + ModuleSource::Module(it) => { + indent = IndentLevel::from_node(it.syntax()) + 1; + it.item_list()?.r_curly_token()?.text_range().start() + } + ModuleSource::BlockExpr(it) => { + it.stmt_list()?.r_curly_token()?.text_range().start() + } + }; + mod_decl_builder.insert(offset, format!("{indent}{mod_decl}\n")); + pub_mod_decl_builder.insert(offset, format!("{indent}{pub_mod_decl}\n")); } } } @@ -167,7 +238,6 @@ fn make_fixes( #[cfg(test)] mod tests { - use crate::tests::{check_diagnostics, check_fix, check_fixes, check_no_fix}; #[test] @@ -333,4 +403,62 @@ mod foo; "#, ); } + + #[test] + fn unlinked_file_insert_into_inline_simple() { + check_fix( + r#" +//- /main.rs +mod bar; +//- /bar.rs +mod foo { +} +//- /bar/foo/baz.rs +$0 +"#, + r#" +mod foo { + mod baz; +} +"#, + ); + } + + #[test] + fn unlinked_file_insert_into_inline_simple_modrs() { + check_fix( + r#" +//- /main.rs +mod bar; +//- /bar.rs +mod baz { +} +//- /bar/baz/foo/mod.rs +$0 +"#, + r#" +mod baz { + mod foo; +} +"#, + ); + } + + #[test] + fn unlinked_file_insert_into_inline_simple_modrs_main() { + check_fix( + r#" +//- /main.rs +mod bar { +} +//- /bar/foo/mod.rs +$0 +"#, + r#" +mod bar { + mod foo; +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs index b2ed19104..9a984ba6b 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs @@ -34,10 +34,7 @@ pub(crate) fn unresolved_proc_macro( let message = format!( "{message}: {}", if config_enabled { - match def_map.proc_macro_loading_error() { - Some(e) => e, - None => "proc macro not found in the built dylib", - } + def_map.proc_macro_loading_error().unwrap_or("proc macro not found in the built dylib") } else { match d.kind { hir::MacroKind::Attr if proc_macros_enabled => { diff --git a/src/tools/rust-analyzer/crates/ide-ssr/Cargo.toml b/src/tools/rust-analyzer/crates/ide-ssr/Cargo.toml index 7be62a8d9..04efa7b91 100644 --- a/src/tools/rust-analyzer/crates/ide-ssr/Cargo.toml +++ b/src/tools/rust-analyzer/crates/ide-ssr/Cargo.toml @@ -2,10 +2,12 @@ name = "ide-ssr" version = "0.0.0" description = "Structural search and replace of Rust code" -license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/rust-analyzer" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -14,13 +16,16 @@ doctest = false cov-mark = "2.0.0-pre.1" itertools = "0.10.5" -text-edit = { path = "../text-edit", version = "0.0.0" } -parser = { path = "../parser", version = "0.0.0" } -syntax = { path = "../syntax", version = "0.0.0" } -ide-db = { path = "../ide-db", version = "0.0.0" } -hir = { path = "../hir", version = "0.0.0" } -stdx = { path = "../stdx", version = "0.0.0" } +# local deps +hir.workspace = true +ide-db.workspace = true +parser.workspace = true +stdx.workspace = true +syntax.workspace = true +text-edit.workspace = true [dev-dependencies] -test-utils = { path = "../test-utils" } expect-test = "1.4.0" + +# local deps +test-utils.workspace = true diff --git a/src/tools/rust-analyzer/crates/ide/Cargo.toml b/src/tools/rust-analyzer/crates/ide/Cargo.toml index 73f202630..30e514e41 100644 --- a/src/tools/rust-analyzer/crates/ide/Cargo.toml +++ b/src/tools/rust-analyzer/crates/ide/Cargo.toml @@ -2,9 +2,11 @@ name = "ide" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -20,28 +22,31 @@ pulldown-cmark-to-cmark = "10.0.4" pulldown-cmark = { version = "0.9.1", default-features = false } url = "2.3.1" dot = "0.1.4" +smallvec.workspace = true -stdx = { path = "../stdx", version = "0.0.0" } -syntax = { path = "../syntax", version = "0.0.0" } -text-edit = { path = "../text-edit", version = "0.0.0" } -ide-db = { path = "../ide-db", version = "0.0.0" } -cfg = { path = "../cfg", version = "0.0.0" } -profile = { path = "../profile", version = "0.0.0" } -ide-assists = { path = "../ide-assists", version = "0.0.0" } -ide-diagnostics = { path = "../ide-diagnostics", version = "0.0.0" } -ide-ssr = { path = "../ide-ssr", version = "0.0.0" } -ide-completion = { path = "../ide-completion", version = "0.0.0" } - +# local deps +cfg.workspace = true +ide-assists.workspace = true +ide-completion.workspace = true +ide-db.workspace = true +ide-diagnostics.workspace = true +ide-ssr.workspace = true +profile.workspace = true +stdx.workspace = true +syntax.workspace = true +text-edit.workspace = true # ide should depend only on the top-level `hir` package. if you need # something from some `hir-xxx` subpackage, reexport the API via `hir`. -hir = { path = "../hir", version = "0.0.0" } +hir.workspace = true [target.'cfg(not(any(target_arch = "wasm32", target_os = "emscripten")))'.dependencies] -toolchain = { path = "../toolchain", version = "0.0.0" } +toolchain.workspace = true [dev-dependencies] -test-utils = { path = "../test-utils" } expect-test = "1.4.0" +# local deps +test-utils.workspace = true + [features] in-rust-tree = ["ide-assists/in-rust-tree", "ide-diagnostics/in-rust-tree"] diff --git a/src/tools/rust-analyzer/crates/ide/src/file_structure.rs b/src/tools/rust-analyzer/crates/ide/src/file_structure.rs index 68fd0952b..b23763dce 100644 --- a/src/tools/rust-analyzer/crates/ide/src/file_structure.rs +++ b/src/tools/rust-analyzer/crates/ide/src/file_structure.rs @@ -160,7 +160,11 @@ fn structure_node(node: &SyntaxNode) -> Option<StructureNode> { let label = match target_trait { None => format!("impl {}", target_type.syntax().text()), Some(t) => { - format!("impl {} for {}", t.syntax().text(), target_type.syntax().text(),) + format!("impl {}{} for {}", + it.excl_token().map(|x| x.to_string()).unwrap_or_default(), + t.syntax().text(), + target_type.syntax().text(), + ) } }; @@ -214,6 +218,29 @@ mod tests { } #[test] + fn test_nagative_trait_bound() { + let txt = r#"impl !Unpin for Test {}"#; + check( + txt, + expect![[r#" + [ + StructureNode { + parent: None, + label: "impl !Unpin for Test", + navigation_range: 16..20, + node_range: 0..23, + kind: SymbolKind( + Impl, + ), + detail: None, + deprecated: false, + }, + ] + "#]], + ); + } + + #[test] fn test_file_structure() { check( r#" diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs index c7130a2a4..e70bc2ec5 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs @@ -17,6 +17,7 @@ use crate::{ // This is the same as `Go to Definition` with the following exceptions: // - outline modules will navigate to the `mod name;` item declaration // - trait assoc items will navigate to the assoc item of the trait declaration opposed to the trait impl +// - fields in patterns will navigate to the field declaration of the struct, union or variant pub(crate) fn goto_declaration( db: &RootDatabase, position: FilePosition, diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs index 73fd518a9..93019527f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs @@ -1916,4 +1916,68 @@ fn main() { "#, ) } + + #[test] + fn query_impls_in_nearest_block() { + check( + r#" +struct S1; +impl S1 { + fn e() -> () {} +} +fn f1() { + struct S1; + impl S1 { + fn e() -> () {} + //^ + } + fn f2() { + fn f3() { + S1::e$0(); + } + } +} +"#, + ); + + check( + r#" +struct S1; +impl S1 { + fn e() -> () {} +} +fn f1() { + struct S1; + impl S1 { + fn e() -> () {} + //^ + } + fn f2() { + struct S2; + S1::e$0(); + } +} +fn f12() { + struct S1; + impl S1 { + fn e() -> () {} + } +} +"#, + ); + + check( + r#" +struct S1; +impl S1 { + fn e() -> () {} + //^ +} +fn f2() { + struct S2; + S1::e$0(); +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs index 55f8779ee..c889eb930 100644 --- a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs +++ b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs @@ -1356,7 +1356,6 @@ fn main() { r#" trait Trait { fn func(self) {} - //^^^^ } impl Trait for () { @@ -1376,7 +1375,6 @@ fn main() { r#" trait Trait { fn func(self) {} - //^^^^ } impl Trait for () { diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index b214fa12a..5f2c61f5b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -15,10 +15,11 @@ use ide_db::{ FxIndexSet, RootDatabase, }; use itertools::Itertools; -use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, T}; +use syntax::{ast, AstNode, SyntaxKind::*, SyntaxNode, T}; use crate::{ doc_links::token_as_doc_comment, + markdown_remove::remove_markdown, markup::Markup, runnables::{runnable_fn, runnable_mod}, FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, TryToNav, @@ -26,14 +27,9 @@ use crate::{ #[derive(Clone, Debug, PartialEq, Eq)] pub struct HoverConfig { pub links_in_hover: bool, - pub documentation: Option<HoverDocFormat>, + pub documentation: bool, pub keywords: bool, -} - -impl HoverConfig { - fn markdown(&self) -> bool { - matches!(self.documentation, Some(HoverDocFormat::Markdown)) - } + pub format: HoverDocFormat, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -90,19 +86,38 @@ pub struct HoverResult { // image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[] pub(crate) fn hover( db: &RootDatabase, - FileRange { file_id, range }: FileRange, + frange @ FileRange { file_id, range }: FileRange, config: &HoverConfig, ) -> Option<RangeInfo<HoverResult>> { let sema = &hir::Semantics::new(db); let file = sema.parse(file_id).syntax().clone(); + let mut res = if range.is_empty() { + hover_simple(sema, FilePosition { file_id, offset: range.start() }, file, config) + } else { + hover_ranged(sema, frange, file, config) + }?; - if !range.is_empty() { - return hover_ranged(&file, range, sema, config); + if let HoverDocFormat::PlainText = config.format { + res.info.markup = remove_markdown(res.info.markup.as_str()).into(); } - let offset = range.start(); + Some(res) +} +fn hover_simple( + sema: &Semantics<'_, RootDatabase>, + FilePosition { file_id, offset }: FilePosition, + file: SyntaxNode, + config: &HoverConfig, +) -> Option<RangeInfo<HoverResult>> { let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind { - IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | T![Self] => 4, + IDENT + | INT_NUMBER + | LIFETIME_IDENT + | T![self] + | T![super] + | T![crate] + | T![Self] + | T![_] => 4, // index and prefix ops T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3, kind if kind.is_keyword() => 2, @@ -135,19 +150,18 @@ pub(crate) fn hover( } else { sema.descend_into_macros_with_same_text(original_token.clone()) }; + let descended = || descended.iter(); - // try lint hover - let result = descended - .iter() + let result = descended() + // try lint hover .find_map(|token| { // FIXME: Definition should include known lints and the like instead of having this special case here let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; render::try_for_lint(&attr, token) }) - // try item definitions + // try definitions .or_else(|| { - descended - .iter() + descended() .filter_map(|token| { let node = token.parent()?; let class = IdentClass::classify_token(sema, token)?; @@ -168,10 +182,12 @@ pub(crate) fn hover( }) }) // try keywords - .or_else(|| descended.iter().find_map(|token| render::keyword(sema, config, token))) - // try rest item hover + .or_else(|| descended().find_map(|token| render::keyword(sema, config, token))) + // try _ hovers + .or_else(|| descended().find_map(|token| render::underscore(sema, config, token))) + // try rest pattern hover .or_else(|| { - descended.iter().find_map(|token| { + descended().find_map(|token| { if token.kind() != DOT2 { return None; } @@ -185,60 +201,43 @@ pub(crate) fn hover( Some(render::struct_rest_pat(sema, config, &record_pat)) }) - }); - - result - .map(|mut res: HoverResult| { - res.actions = dedupe_or_merge_hover_actions(res.actions); - RangeInfo::new(original_token.text_range(), res) }) - // fallback to type hover if there aren't any other suggestions - // this finds its own range instead of using the closest token's range + // try () call hovers .or_else(|| { - descended.iter().find_map(|token| hover_type_fallback(sema, config, token, token)) - }) -} + descended().find_map(|token| { + if token.kind() != T!['('] && token.kind() != T![')'] { + return None; + } + let arg_list = token.parent().and_then(ast::ArgList::cast)?.syntax().parent()?; + let call_expr = syntax::match_ast! { + match arg_list { + ast::CallExpr(expr) => expr.into(), + ast::MethodCallExpr(expr) => expr.into(), + _ => return None, + } + }; + render::type_info_of(sema, config, &Either::Left(call_expr)) + }) + }); -pub(crate) fn hover_for_definition( - sema: &Semantics<'_, RootDatabase>, - file_id: FileId, - definition: Definition, - node: &SyntaxNode, - config: &HoverConfig, -) -> Option<HoverResult> { - let famous_defs = match &definition { - Definition::BuiltinType(_) => Some(FamousDefs(sema, sema.scope(node)?.krate())), - _ => None, - }; - render::definition(sema.db, definition, famous_defs.as_ref(), config).map(|markup| { - HoverResult { - markup: render::process_markup(sema.db, definition, &markup, config), - actions: show_implementations_action(sema.db, definition) - .into_iter() - .chain(show_fn_references_action(sema.db, definition)) - .chain(runnable_action(sema, definition, file_id)) - .chain(goto_type_action_for_def(sema.db, definition)) - .collect(), - } + result.map(|mut res: HoverResult| { + res.actions = dedupe_or_merge_hover_actions(res.actions); + RangeInfo::new(original_token.text_range(), res) }) } fn hover_ranged( - file: &SyntaxNode, - range: syntax::TextRange, sema: &Semantics<'_, RootDatabase>, + FileRange { range, .. }: FileRange, + file: SyntaxNode, config: &HoverConfig, ) -> Option<RangeInfo<HoverResult>> { // FIXME: make this work in attributes - let expr_or_pat = file.covering_element(range).ancestors().find_map(|it| { - match_ast! { - match it { - ast::Expr(expr) => Some(Either::Left(expr)), - ast::Pat(pat) => Some(Either::Right(pat)), - _ => None, - } - } - })?; + let expr_or_pat = file + .covering_element(range) + .ancestors() + .take_while(|it| ast::MacroCall::can_cast(it.kind()) || !ast::Item::can_cast(it.kind())) + .find_map(Either::<ast::Expr, ast::Pat>::cast)?; let res = match &expr_or_pat { Either::Left(ast::Expr::TryExpr(try_expr)) => render::try_expr(sema, config, try_expr), Either::Left(ast::Expr::PrefixExpr(prefix_expr)) @@ -248,7 +247,7 @@ fn hover_ranged( } _ => None, }; - let res = res.or_else(|| render::type_info(sema, config, &expr_or_pat)); + let res = res.or_else(|| render::type_info_of(sema, config, &expr_or_pat)); res.map(|it| { let range = match expr_or_pat { Either::Left(it) => it.syntax().text_range(), @@ -258,37 +257,31 @@ fn hover_ranged( }) } -fn hover_type_fallback( +pub(crate) fn hover_for_definition( sema: &Semantics<'_, RootDatabase>, + file_id: FileId, + definition: Definition, + node: &SyntaxNode, config: &HoverConfig, - token: &SyntaxToken, - original_token: &SyntaxToken, -) -> Option<RangeInfo<HoverResult>> { - let node = - token.parent_ancestors().take_while(|it| !ast::Item::can_cast(it.kind())).find(|n| { - ast::Expr::can_cast(n.kind()) - || ast::Pat::can_cast(n.kind()) - || ast::Type::can_cast(n.kind()) - })?; - - let expr_or_pat = match_ast! { - match node { - ast::Expr(it) => Either::Left(it), - ast::Pat(it) => Either::Right(it), - // If this node is a MACRO_CALL, it means that `descend_into_macros_many` failed to resolve. - // (e.g expanding a builtin macro). So we give up here. - ast::MacroCall(_it) => return None, - _ => return None, - } +) -> Option<HoverResult> { + let famous_defs = match &definition { + Definition::BuiltinType(_) => Some(FamousDefs(sema, sema.scope(node)?.krate())), + _ => None, }; - - let res = render::type_info(sema, config, &expr_or_pat)?; - - let range = sema - .original_range_opt(&node) - .map(|frange| frange.range) - .unwrap_or_else(|| original_token.text_range()); - Some(RangeInfo::new(range, res)) + render::definition(sema.db, definition, famous_defs.as_ref(), config).map(|markup| { + HoverResult { + markup: render::process_markup(sema.db, definition, &markup, config), + actions: [ + show_implementations_action(sema.db, definition), + show_fn_references_action(sema.db, definition), + runnable_action(sema, definition, file_id), + goto_type_action_for_def(sema.db, definition), + ] + .into_iter() + .flatten() + .collect(), + } + }) } fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> { diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs index 47257f0bf..22611cfb8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs @@ -26,58 +26,24 @@ use syntax::{ use crate::{ doc_links::{remove_links, rewrite_links}, hover::walk_and_push_ty, - markdown_remove::remove_markdown, HoverAction, HoverConfig, HoverResult, Markup, }; -pub(super) fn type_info( +pub(super) fn type_info_of( sema: &Semantics<'_, RootDatabase>, - config: &HoverConfig, + _config: &HoverConfig, expr_or_pat: &Either<ast::Expr, ast::Pat>, ) -> Option<HoverResult> { let TypeInfo { original, adjusted } = match expr_or_pat { Either::Left(expr) => sema.type_of_expr(expr)?, Either::Right(pat) => sema.type_of_pat(pat)?, }; - - let mut res = HoverResult::default(); - let mut targets: Vec<hir::ModuleDef> = Vec::new(); - let mut push_new_def = |item: hir::ModuleDef| { - if !targets.contains(&item) { - targets.push(item); - } - }; - walk_and_push_ty(sema.db, &original, &mut push_new_def); - - res.markup = if let Some(adjusted_ty) = adjusted { - walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def); - let original = original.display(sema.db).to_string(); - let adjusted = adjusted_ty.display(sema.db).to_string(); - let static_text_diff_len = "Coerced to: ".len() - "Type: ".len(); - format!( - "{bt_start}Type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}", - original, - adjusted, - apad = static_text_diff_len + adjusted.len().max(original.len()), - opad = original.len(), - bt_start = if config.markdown() { "```text\n" } else { "" }, - bt_end = if config.markdown() { "```\n" } else { "" } - ) - .into() - } else { - if config.markdown() { - Markup::fenced_block(&original.display(sema.db)) - } else { - original.display(sema.db).to_string().into() - } - }; - res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets)); - Some(res) + type_info(sema, _config, original, adjusted) } pub(super) fn try_expr( sema: &Semantics<'_, RootDatabase>, - config: &HoverConfig, + _config: &HoverConfig, try_expr: &ast::TryExpr, ) -> Option<HoverResult> { let inner_ty = sema.type_of_expr(&try_expr.expr()?)?.original; @@ -153,14 +119,12 @@ pub(super) fn try_expr( let ppad = static_text_len_diff.min(0).abs() as usize; res.markup = format!( - "{bt_start}{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n{bt_end}", + "```text\n{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n```\n", s, inner_ty, body_ty, pad0 = ty_len_max + tpad, pad1 = ty_len_max + ppad, - bt_start = if config.markdown() { "```text\n" } else { "" }, - bt_end = if config.markdown() { "```\n" } else { "" } ) .into(); Some(res) @@ -168,7 +132,7 @@ pub(super) fn try_expr( pub(super) fn deref_expr( sema: &Semantics<'_, RootDatabase>, - config: &HoverConfig, + _config: &HoverConfig, deref_expr: &ast::PrefixExpr, ) -> Option<HoverResult> { let inner_ty = sema.type_of_expr(&deref_expr.expr()?)?.original; @@ -197,15 +161,13 @@ pub(super) fn deref_expr( .max(adjusted.len() + coerced_len) .max(inner.len() + deref_len); format!( - "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}", + "```text\nDereferenced from: {:>ipad$}\nTo type: {:>apad$}\nCoerced to: {:>opad$}\n```\n", inner, original, adjusted, ipad = max_len - deref_len, apad = max_len - type_len, opad = max_len - coerced_len, - bt_start = if config.markdown() { "```text\n" } else { "" }, - bt_end = if config.markdown() { "```\n" } else { "" } ) .into() } else { @@ -215,13 +177,11 @@ pub(super) fn deref_expr( let deref_len = "Dereferenced from: ".len(); let max_len = (original.len() + type_len).max(inner.len() + deref_len); format!( - "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\n{bt_end}", + "```text\nDereferenced from: {:>ipad$}\nTo type: {:>apad$}\n```\n", inner, original, ipad = max_len - deref_len, apad = max_len - type_len, - bt_start = if config.markdown() { "```text\n" } else { "" }, - bt_end = if config.markdown() { "```\n" } else { "" } ) .into() }; @@ -230,12 +190,54 @@ pub(super) fn deref_expr( Some(res) } +pub(super) fn underscore( + sema: &Semantics<'_, RootDatabase>, + config: &HoverConfig, + token: &SyntaxToken, +) -> Option<HoverResult> { + if token.kind() != T![_] { + return None; + } + let parent = token.parent()?; + let _it = match_ast! { + match parent { + ast::InferType(it) => it, + ast::UnderscoreExpr(it) => return type_info_of(sema, config, &Either::Left(ast::Expr::UnderscoreExpr(it))), + ast::WildcardPat(it) => return type_info_of(sema, config, &Either::Right(ast::Pat::WildcardPat(it))), + _ => return None, + } + }; + // let it = infer_type.syntax().parent()?; + // match_ast! { + // match it { + // ast::LetStmt(_it) => (), + // ast::Param(_it) => (), + // ast::RetType(_it) => (), + // ast::TypeArg(_it) => (), + + // ast::CastExpr(_it) => (), + // ast::ParenType(_it) => (), + // ast::TupleType(_it) => (), + // ast::PtrType(_it) => (), + // ast::RefType(_it) => (), + // ast::ArrayType(_it) => (), + // ast::SliceType(_it) => (), + // ast::ForType(_it) => (), + // _ => return None, + // } + // } + + // FIXME: https://github.com/rust-lang/rust-analyzer/issues/11762, this currently always returns Unknown + // type_info(sema, config, sema.resolve_type(&ast::Type::InferType(it))?, None) + None +} + pub(super) fn keyword( sema: &Semantics<'_, RootDatabase>, config: &HoverConfig, token: &SyntaxToken, ) -> Option<HoverResult> { - if !token.kind().is_keyword() || !config.documentation.is_some() || !config.keywords { + if !token.kind().is_keyword() || !config.documentation || !config.keywords { return None; } let parent = token.parent()?; @@ -259,7 +261,7 @@ pub(super) fn keyword( /// i.e. `let S {a, ..} = S {a: 1, b: 2}` pub(super) fn struct_rest_pat( sema: &Semantics<'_, RootDatabase>, - config: &HoverConfig, + _config: &HoverConfig, pattern: &RecordPat, ) -> HoverResult { let missing_fields = sema.record_pattern_missing_fields(pattern); @@ -288,11 +290,7 @@ pub(super) fn struct_rest_pat( // get rid of trailing comma s.truncate(s.len() - 2); - if config.markdown() { - Markup::fenced_block(&s) - } else { - s.into() - } + Markup::fenced_block(&s) }; res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets)); res @@ -346,13 +344,8 @@ pub(super) fn process_markup( config: &HoverConfig, ) -> Markup { let markup = markup.as_str(); - let markup = if !config.markdown() { - remove_markdown(markup) - } else if config.links_in_hover { - rewrite_links(db, markup, def) - } else { - remove_links(markup) - }; + let markup = + if config.links_in_hover { rewrite_links(db, markup, def) } else { remove_links(markup) }; Markup::from(markup) } @@ -465,8 +458,9 @@ pub(super) fn definition( Definition::DeriveHelper(it) => (format!("derive_helper {}", it.name(db)), None), }; - let docs = match config.documentation { - Some(_) => docs.or_else(|| { + let docs = docs + .filter(|_| config.documentation) + .or_else(|| { // docs are missing, for assoc items of trait impls try to fall back to the docs of the // original item of the trait let assoc = def.as_assoc_item(db)?; @@ -474,13 +468,46 @@ pub(super) fn definition( let name = Some(assoc.name(db)?); let item = trait_.items(db).into_iter().find(|it| it.name(db) == name)?; item.docs(db) - }), - None => None, - }; - let docs = docs.filter(|_| config.documentation.is_some()).map(Into::into); + }) + .map(Into::into); markup(docs, label, mod_path) } +fn type_info( + sema: &Semantics<'_, RootDatabase>, + _config: &HoverConfig, + original: hir::Type, + adjusted: Option<hir::Type>, +) -> Option<HoverResult> { + let mut res = HoverResult::default(); + let mut targets: Vec<hir::ModuleDef> = Vec::new(); + let mut push_new_def = |item: hir::ModuleDef| { + if !targets.contains(&item) { + targets.push(item); + } + }; + walk_and_push_ty(sema.db, &original, &mut push_new_def); + + res.markup = if let Some(adjusted_ty) = adjusted { + walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def); + let original = original.display(sema.db).to_string(); + let adjusted = adjusted_ty.display(sema.db).to_string(); + let static_text_diff_len = "Coerced to: ".len() - "Type: ".len(); + format!( + "```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n```\n", + original, + adjusted, + apad = static_text_diff_len + adjusted.len().max(original.len()), + opad = original.len(), + ) + .into() + } else { + Markup::fenced_block(&original.display(sema.db)) + }; + res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets)); + Some(res) +} + fn render_builtin_attr(db: &RootDatabase, attr: hir::BuiltinAttr) -> Option<Markup> { let name = attr.name(db); let desc = format!("#[{name}]"); diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs index c7f241f2f..bd7ce2f1d 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -2,7 +2,7 @@ use expect_test::{expect, Expect}; use ide_db::base_db::{FileLoader, FileRange}; use syntax::TextRange; -use crate::{fixture, hover::HoverDocFormat, HoverConfig}; +use crate::{fixture, HoverConfig, HoverDocFormat}; fn check_hover_no_result(ra_fixture: &str) { let (analysis, position) = fixture::position(ra_fixture); @@ -10,8 +10,9 @@ fn check_hover_no_result(ra_fixture: &str) { .hover( &HoverConfig { links_in_hover: true, - documentation: Some(HoverDocFormat::Markdown), + documentation: true, keywords: true, + format: HoverDocFormat::Markdown, }, FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, ) @@ -26,8 +27,9 @@ fn check(ra_fixture: &str, expect: Expect) { .hover( &HoverConfig { links_in_hover: true, - documentation: Some(HoverDocFormat::Markdown), + documentation: true, keywords: true, + format: HoverDocFormat::Markdown, }, FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, ) @@ -47,8 +49,9 @@ fn check_hover_no_links(ra_fixture: &str, expect: Expect) { .hover( &HoverConfig { links_in_hover: false, - documentation: Some(HoverDocFormat::Markdown), + documentation: true, keywords: true, + format: HoverDocFormat::Markdown, }, FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, ) @@ -68,8 +71,9 @@ fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) { .hover( &HoverConfig { links_in_hover: true, - documentation: Some(HoverDocFormat::PlainText), + documentation: true, keywords: true, + format: HoverDocFormat::PlainText, }, FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, ) @@ -89,8 +93,9 @@ fn check_actions(ra_fixture: &str, expect: Expect) { .hover( &HoverConfig { links_in_hover: true, - documentation: Some(HoverDocFormat::Markdown), + documentation: true, keywords: true, + format: HoverDocFormat::Markdown, }, FileRange { file_id, range: position.range_or_empty() }, ) @@ -105,8 +110,9 @@ fn check_hover_range(ra_fixture: &str, expect: Expect) { .hover( &HoverConfig { links_in_hover: false, - documentation: Some(HoverDocFormat::Markdown), + documentation: true, keywords: true, + format: HoverDocFormat::Markdown, }, range, ) @@ -121,8 +127,9 @@ fn check_hover_range_no_results(ra_fixture: &str) { .hover( &HoverConfig { links_in_hover: false, - documentation: Some(HoverDocFormat::Markdown), + documentation: true, keywords: true, + format: HoverDocFormat::Markdown, }, range, ) @@ -207,37 +214,20 @@ m!(ab$0c); } #[test] -fn hover_shows_type_of_an_expression() { - check( - r#" -pub fn foo() -> u32 { 1 } - -fn main() { - let foo_test = foo()$0; -} -"#, - expect![[r#" - *foo()* - ```rust - u32 - ``` - "#]], - ); -} - -#[test] fn hover_remove_markdown_if_configured() { check_hover_no_markdown( r#" pub fn foo() -> u32 { 1 } fn main() { - let foo_test = foo()$0; + let foo_test = foo$0(); } "#, expect![[r#" - *foo()* - u32 + *foo* + test + + pub fn foo() -> u32 "#]], ); } @@ -297,33 +287,6 @@ fn main() { let foo_test = fo$0o(); } "#]], ); - // Multiple candidates but results are ambiguous. - check( - r#" -//- /a.rs -pub fn foo() -> u32 { 1 } - -//- /b.rs -pub fn foo() -> &str { "" } - -//- /c.rs -pub fn foo(a: u32, b: u32) {} - -//- /main.rs -mod a; -mod b; -mod c; - -fn main() { let foo_test = fo$0o(); } - "#, - expect![[r#" - *foo* - ```rust - {unknown} - ``` - "#]], - ); - // Use literal `crate` in path check( r#" @@ -527,6 +490,7 @@ fn hover_field_offset() { // Hovering over the field when instantiating check( r#" +//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 struct Foo { fiel$0d_a: u8, field_b: i32, field_c: i16 } "#, expect![[r#" @@ -548,6 +512,7 @@ fn hover_shows_struct_field_info() { // Hovering over the field when instantiating check( r#" +//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 struct Foo { field_a: u32 } fn main() { @@ -570,6 +535,7 @@ fn main() { // Hovering over the field in the definition check( r#" +//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 struct Foo { field_a$0: u32 } fn main() { @@ -1184,33 +1150,19 @@ fn test_hover_through_func_in_macro_recursive() { macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } } macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } } fn bar() -> u32 { 0 } -fn foo() { let a = id!([0u32, bar($0)] ); } +fn foo() { let a = id!([0u32, bar$0()] ); } "#, expect![[r#" - *bar()* - ```rust - u32 - ``` - "#]], - ); -} + *bar* -#[test] -fn test_hover_through_literal_string_in_macro() { - check( - r#" -macro_rules! arr { ($($tt:tt)*) => { [$($tt)*] } } -fn foo() { - let mastered_for_itunes = ""; - let _ = arr!("Tr$0acks", &mastered_for_itunes); -} -"#, - expect![[r#" - *"Tracks"* - ```rust - &str - ``` - "#]], + ```rust + test + ``` + + ```rust + fn bar() -> u32 + ``` + "#]], ); } @@ -1515,6 +1467,8 @@ fn my() {} fn test_hover_struct_doc_comment() { check( r#" +//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 + /// This is an example /// multiline doc /// @@ -1573,7 +1527,7 @@ fn foo() { let bar = Ba$0r; } ``` ```rust - struct Bar // size = 0, align = 1 + struct Bar ``` --- @@ -1602,7 +1556,7 @@ fn foo() { let bar = Ba$0r; } ``` ```rust - struct Bar // size = 0, align = 1 + struct Bar ``` --- @@ -1630,7 +1584,7 @@ pub struct B$0ar ``` ```rust - pub struct Bar // size = 0, align = 1 + pub struct Bar ``` --- @@ -1657,7 +1611,7 @@ pub struct B$0ar ``` ```rust - pub struct Bar // size = 0, align = 1 + pub struct Bar ``` --- @@ -2959,6 +2913,8 @@ fn main() { let foo_test = name_with_dashes::wrapper::Thing::new$0(); } fn hover_field_pat_shorthand_ref_match_ergonomics() { check( r#" +//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 + struct S { f: i32, } @@ -4398,6 +4354,7 @@ fn main() { fn hover_intra_doc_links() { check( r#" +//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 pub mod theitem { /// This is the item. Cool! @@ -4539,7 +4496,7 @@ trait A where fn string_shadowed_with_inner_items() { check( r#" -//- /main.rs crate:main deps:alloc +//- /main.rs crate:main deps:alloc target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 /// Custom `String` type. struct String; @@ -5234,7 +5191,7 @@ foo_macro!( ``` ```rust - pub struct Foo // size = 0, align = 1 + pub struct Foo ``` --- @@ -5248,6 +5205,8 @@ foo_macro!( fn hover_intra_in_attr() { check( r#" +//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 + #[doc = "Doc comment for [`Foo$0`]"] pub struct Foo(i32); "#, @@ -5368,6 +5327,8 @@ enum Enum { fn hover_record_variant_field() { check( r#" +//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 + enum Enum { RecordV { field$0: u32 } } @@ -5573,3 +5534,116 @@ fn main() { "#]], ); } + +#[test] +fn hover_underscore_pat() { + check( + r#" +fn main() { + let _$0 = 0; +} +"#, + expect![[r#" + *_* + ```rust + i32 + ``` + "#]], + ); + check( + r#" +fn main() { + let (_$0,) = (0,); +} +"#, + expect![[r#" + *_* + ```rust + i32 + ``` + "#]], + ); +} + +#[test] +fn hover_underscore_expr() { + check( + r#" +fn main() { + _$0 = 0; +} +"#, + expect![[r#" + *_* + ```rust + i32 + ``` + "#]], + ); + check( + r#" +fn main() { + (_$0,) = (0,); +} +"#, + expect![[r#" + *_* + ```rust + i32 + ``` + "#]], + ); +} + +#[test] +fn hover_underscore_type() { + check_hover_no_result( + r#" +fn main() { + let x: _$0 = 0; +} +"#, + ); + check_hover_no_result( + r#" +fn main() { + let x: (_$0,) = (0,); +} +"#, + ); +} + +#[test] +fn hover_call_parens() { + check( + r#" +fn foo() -> i32 {} +fn main() { + foo($0); +} +"#, + expect![[r#" + *)* + ```rust + i32 + ``` + "#]], + ); + check( + r#" +struct S; +impl S { + fn foo(self) -> i32 {} +} +fn main() { + S.foo($0); +} +"#, + expect![[r#" + *)* + ```rust + i32 + ``` + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs index 48a7bbfec..ac477339e 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs @@ -4,13 +4,16 @@ use std::{ }; use either::Either; -use hir::{known, HasVisibility, HirDisplay, HirWrite, ModuleDef, ModuleDefId, Semantics}; +use hir::{ + known, HasVisibility, HirDisplay, HirDisplayError, HirWrite, ModuleDef, ModuleDefId, Semantics, +}; use ide_db::{base_db::FileRange, famous_defs::FamousDefs, RootDatabase}; use itertools::Itertools; +use smallvec::{smallvec, SmallVec}; use stdx::never; use syntax::{ ast::{self, AstNode}, - match_ast, NodeOrToken, SyntaxNode, TextRange, TextSize, + match_ast, NodeOrToken, SyntaxNode, TextRange, }; use crate::{navigation_target::TryToNav, FileId}; @@ -28,7 +31,6 @@ mod discriminant; #[derive(Clone, Debug, PartialEq, Eq)] pub struct InlayHintsConfig { - pub location_links: bool, pub render_colons: bool, pub type_hints: bool, pub discriminant_hints: DiscriminantHints, @@ -83,75 +85,108 @@ pub enum AdjustmentHintsMode { PreferPostfix, } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum InlayKind { - BindingModeHint, - ChainingHint, - ClosingBraceHint, - ClosureReturnTypeHint, - GenericParamListHint, - AdjustmentHint, - AdjustmentHintPostfix, - LifetimeHint, - ParameterHint, - TypeHint, - DiscriminantHint, + BindingMode, + Chaining, + ClosingBrace, + ClosureReturnType, + GenericParamList, + Adjustment, + AdjustmentPostfix, + Lifetime, + Parameter, + Type, + Discriminant, OpeningParenthesis, ClosingParenthesis, } #[derive(Debug)] pub struct InlayHint { + /// The text range this inlay hint applies to. pub range: TextRange, + /// The kind of this inlay hint. This is used to determine side and padding of the hint for + /// rendering purposes. pub kind: InlayKind, + /// The actual label to show in the inlay hint. pub label: InlayHintLabel, - pub tooltip: Option<InlayTooltip>, +} + +impl InlayHint { + fn closing_paren(range: TextRange) -> InlayHint { + InlayHint { range, kind: InlayKind::ClosingParenthesis, label: InlayHintLabel::from(")") } + } + fn opening_paren(range: TextRange) -> InlayHint { + InlayHint { range, kind: InlayKind::OpeningParenthesis, label: InlayHintLabel::from("(") } + } } #[derive(Debug)] pub enum InlayTooltip { String(String), - HoverRanged(FileId, TextRange), - HoverOffset(FileId, TextSize), + Markdown(String), } #[derive(Default)] pub struct InlayHintLabel { - pub parts: Vec<InlayHintLabelPart>, + pub parts: SmallVec<[InlayHintLabelPart; 1]>, } impl InlayHintLabel { - pub fn as_simple_str(&self) -> Option<&str> { - match &*self.parts { - [part] => part.as_simple_str(), - _ => None, + pub fn simple( + s: impl Into<String>, + tooltip: Option<InlayTooltip>, + linked_location: Option<FileRange>, + ) -> InlayHintLabel { + InlayHintLabel { + parts: smallvec![InlayHintLabelPart { text: s.into(), linked_location, tooltip }], } } pub fn prepend_str(&mut self, s: &str) { match &mut *self.parts { - [part, ..] if part.as_simple_str().is_some() => part.text = format!("{s}{}", part.text), - _ => self.parts.insert(0, InlayHintLabelPart { text: s.into(), linked_location: None }), + [InlayHintLabelPart { text, linked_location: None, tooltip: None }, ..] => { + text.insert_str(0, s) + } + _ => self.parts.insert( + 0, + InlayHintLabelPart { text: s.into(), linked_location: None, tooltip: None }, + ), } } pub fn append_str(&mut self, s: &str) { match &mut *self.parts { - [.., part] if part.as_simple_str().is_some() => part.text.push_str(s), - _ => self.parts.push(InlayHintLabelPart { text: s.into(), linked_location: None }), + [.., InlayHintLabelPart { text, linked_location: None, tooltip: None }] => { + text.push_str(s) + } + _ => self.parts.push(InlayHintLabelPart { + text: s.into(), + linked_location: None, + tooltip: None, + }), } } } impl From<String> for InlayHintLabel { fn from(s: String) -> Self { - Self { parts: vec![InlayHintLabelPart { text: s, linked_location: None }] } + Self { + parts: smallvec![InlayHintLabelPart { text: s, linked_location: None, tooltip: None }], + } } } impl From<&str> for InlayHintLabel { fn from(s: &str) -> Self { - Self { parts: vec![InlayHintLabelPart { text: s.into(), linked_location: None }] } + Self { + parts: smallvec![InlayHintLabelPart { + text: s.into(), + linked_location: None, + tooltip: None + }], + } } } @@ -175,25 +210,25 @@ pub struct InlayHintLabelPart { /// When setting this, no tooltip must be set on the containing hint, or VS Code will display /// them both. pub linked_location: Option<FileRange>, -} - -impl InlayHintLabelPart { - pub fn as_simple_str(&self) -> Option<&str> { - match self { - Self { text, linked_location: None } => Some(text), - _ => None, - } - } + /// The tooltip to show when hovering over the inlay hint, this may invoke other actions like + /// hover requests to show. + pub tooltip: Option<InlayTooltip>, } impl fmt::Debug for InlayHintLabelPart { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.as_simple_str() { - Some(string) => string.fmt(f), - None => f + match self { + Self { text, linked_location: None, tooltip: None } => text.fmt(f), + Self { text, linked_location, tooltip } => f .debug_struct("InlayHintLabelPart") - .field("text", &self.text) - .field("linked_location", &self.linked_location) + .field("text", text) + .field("linked_location", linked_location) + .field( + "tooltip", + &tooltip.as_ref().map_or("", |it| match it { + InlayTooltip::String(it) | InlayTooltip::Markdown(it) => it, + }), + ) .finish(), } } @@ -204,7 +239,6 @@ struct InlayHintLabelBuilder<'a> { db: &'a RootDatabase, result: InlayHintLabel, last_part: String, - location_link_enabled: bool, location: Option<FileRange>, } @@ -216,9 +250,6 @@ impl fmt::Write for InlayHintLabelBuilder<'_> { impl HirWrite for InlayHintLabelBuilder<'_> { fn start_location_link(&mut self, def: ModuleDefId) { - if !self.location_link_enabled { - return; - } if self.location.is_some() { never!("location link is already started"); } @@ -230,9 +261,6 @@ impl HirWrite for InlayHintLabelBuilder<'_> { } fn end_location_link(&mut self) { - if !self.location_link_enabled { - return; - } self.make_new_part(); } } @@ -242,6 +270,7 @@ impl InlayHintLabelBuilder<'_> { self.result.parts.push(InlayHintLabelPart { text: take(&mut self.last_part), linked_location: self.location.take(), + tooltip: None, }); } @@ -262,34 +291,51 @@ fn label_of_ty( mut max_length: Option<usize>, ty: hir::Type, label_builder: &mut InlayHintLabelBuilder<'_>, - ) { + ) -> Result<(), HirDisplayError> { let iter_item_type = hint_iterator(sema, famous_defs, &ty); match iter_item_type { - Some(ty) => { - const LABEL_START: &str = "impl Iterator<Item = "; + Some((iter_trait, item, ty)) => { + const LABEL_START: &str = "impl "; + const LABEL_ITERATOR: &str = "Iterator"; + const LABEL_MIDDLE: &str = "<"; + const LABEL_ITEM: &str = "Item"; + const LABEL_MIDDLE2: &str = " = "; const LABEL_END: &str = ">"; - max_length = - max_length.map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len())); - - label_builder.write_str(LABEL_START).unwrap(); - rec(sema, famous_defs, max_length, ty, label_builder); - label_builder.write_str(LABEL_END).unwrap(); - } - None => { - let _ = ty.display_truncated(sema.db, max_length).write_to(label_builder); + max_length = max_length.map(|len| { + len.saturating_sub( + LABEL_START.len() + + LABEL_ITERATOR.len() + + LABEL_MIDDLE.len() + + LABEL_MIDDLE2.len() + + LABEL_END.len(), + ) + }); + + label_builder.write_str(LABEL_START)?; + label_builder.start_location_link(ModuleDef::from(iter_trait).into()); + label_builder.write_str(LABEL_ITERATOR)?; + label_builder.end_location_link(); + label_builder.write_str(LABEL_MIDDLE)?; + label_builder.start_location_link(ModuleDef::from(item).into()); + label_builder.write_str(LABEL_ITEM)?; + label_builder.end_location_link(); + label_builder.write_str(LABEL_MIDDLE2)?; + rec(sema, famous_defs, max_length, ty, label_builder)?; + label_builder.write_str(LABEL_END)?; + Ok(()) } - }; + None => ty.display_truncated(sema.db, max_length).write_to(label_builder), + } } let mut label_builder = InlayHintLabelBuilder { db: sema.db, last_part: String::new(), location: None, - location_link_enabled: config.location_links, result: InlayHintLabel::default(), }; - rec(sema, famous_defs, config.max_length, ty, &mut label_builder); + let _ = rec(sema, famous_defs, config.max_length, ty, &mut label_builder); let r = label_builder.finish(); Some(r) } @@ -383,11 +429,9 @@ fn hints( // static type elisions ast::Item::Static(it) => implicit_static::hints(hints, config, Either::Left(it)), ast::Item::Const(it) => implicit_static::hints(hints, config, Either::Right(it)), + ast::Item::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, file_id, it), _ => None, }, - ast::Variant(v) => { - discriminant::hints(hints, famous_defs, config, file_id, &v) - }, // FIXME: fn-ptr type, dyn fn type, and trait object type elisions ast::Type(_) => None, _ => None, @@ -395,12 +439,12 @@ fn hints( }; } -/// Checks if the type is an Iterator from std::iter and returns its item type. +/// Checks if the type is an Iterator from std::iter and returns the iterator trait and the item type of the concrete iterator. fn hint_iterator( sema: &Semantics<'_, RootDatabase>, famous_defs: &FamousDefs<'_, '_>, ty: &hir::Type, -) -> Option<hir::Type> { +) -> Option<(hir::Trait, hir::TypeAlias, hir::Type)> { let db = sema.db; let strukt = ty.strip_references().as_adt()?; let krate = strukt.module(db).krate(); @@ -423,7 +467,7 @@ fn hint_iterator( _ => None, })?; if let Some(ty) = ty.normalize_trait_assoc_type(db, &[], assoc_type_item) { - return Some(ty); + return Some((iter_trait, assoc_type_item, ty)); } } @@ -447,7 +491,6 @@ mod tests { use super::ClosureReturnTypeHints; pub(super) const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig { - location_links: false, discriminant_hints: DiscriminantHints::Never, render_colons: false, type_hints: false, @@ -465,8 +508,6 @@ mod tests { max_length: None, closing_brace_hints_min_lines: None, }; - pub(super) const DISABLED_CONFIG_WITH_LINKS: InlayHintsConfig = - InlayHintsConfig { location_links: true, ..DISABLED_CONFIG }; pub(super) const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig { type_hints: true, parameter_hints: true, @@ -474,7 +515,7 @@ mod tests { closure_return_type_hints: ClosureReturnTypeHints::WithBlock, binding_mode_hints: true, lifetime_elision_hints: LifetimeElisionHints::Always, - ..DISABLED_CONFIG_WITH_LINKS + ..DISABLED_CONFIG }; #[track_caller] diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs index bdd7c05e0..188eb7f97 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs @@ -3,15 +3,19 @@ //! let _: u32 = /* <never-to-any> */ loop {}; //! let _: &u32 = /* &* */ &mut 0; //! ``` -use hir::{Adjust, AutoBorrow, Mutability, OverloadedDeref, PointerCast, Safety, Semantics}; +use hir::{Adjust, Adjustment, AutoBorrow, HirDisplay, Mutability, PointerCast, Safety, Semantics}; use ide_db::RootDatabase; +use stdx::never; use syntax::{ ast::{self, make, AstNode}, ted, }; -use crate::{AdjustmentHints, AdjustmentHintsMode, InlayHint, InlayHintsConfig, InlayKind}; +use crate::{ + AdjustmentHints, AdjustmentHintsMode, InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind, + InlayTooltip, +}; pub(super) fn hints( acc: &mut Vec<InlayHint>, @@ -44,27 +48,12 @@ pub(super) fn hints( mode_and_needs_parens_for_adjustment_hints(expr, config.adjustment_hints_mode); if needs_outer_parens { - acc.push(InlayHint { - range: expr.syntax().text_range(), - kind: InlayKind::OpeningParenthesis, - label: "(".into(), - tooltip: None, - }); + acc.push(InlayHint::opening_paren(expr.syntax().text_range())); } if postfix && needs_inner_parens { - acc.push(InlayHint { - range: expr.syntax().text_range(), - kind: InlayKind::OpeningParenthesis, - label: "(".into(), - tooltip: None, - }); - acc.push(InlayHint { - range: expr.syntax().text_range(), - kind: InlayKind::ClosingParenthesis, - label: ")".into(), - tooltip: None, - }); + acc.push(InlayHint::opening_paren(expr.syntax().text_range())); + acc.push(InlayHint::closing_paren(expr.syntax().text_range())); } let (mut tmp0, mut tmp1); @@ -76,72 +65,71 @@ pub(super) fn hints( &mut tmp1 }; - for adjustment in iter { - if adjustment.source == adjustment.target { + for Adjustment { source, target, kind } in iter { + if source == target { continue; } // FIXME: Add some nicer tooltips to each of these - let text = match adjustment.kind { + let (text, coercion) = match kind { Adjust::NeverToAny if config.adjustment_hints == AdjustmentHints::Always => { - "<never-to-any>" + ("<never-to-any>", "never to any") + } + Adjust::Deref(_) => ("*", "dereference"), + Adjust::Borrow(AutoBorrow::Ref(Mutability::Shared)) => ("&", "borrow"), + Adjust::Borrow(AutoBorrow::Ref(Mutability::Mut)) => ("&mut ", "unique borrow"), + Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Shared)) => { + ("&raw const ", "const pointer borrow") + } + Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Mut)) => { + ("&raw mut ", "mut pointer borrow") } - Adjust::Deref(None) => "*", - Adjust::Deref(Some(OverloadedDeref(Mutability::Mut))) => "*", - Adjust::Deref(Some(OverloadedDeref(Mutability::Shared))) => "*", - Adjust::Borrow(AutoBorrow::Ref(Mutability::Shared)) => "&", - Adjust::Borrow(AutoBorrow::Ref(Mutability::Mut)) => "&mut ", - Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Shared)) => "&raw const ", - Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Mut)) => "&raw mut ", // some of these could be represented via `as` casts, but that's not too nice and // handling everything as a prefix expr makes the `(` and `)` insertion easier Adjust::Pointer(cast) if config.adjustment_hints == AdjustmentHints::Always => { match cast { - PointerCast::ReifyFnPointer => "<fn-item-to-fn-pointer>", - PointerCast::UnsafeFnPointer => "<safe-fn-pointer-to-unsafe-fn-pointer>", + PointerCast::ReifyFnPointer => { + ("<fn-item-to-fn-pointer>", "fn item to fn pointer") + } + PointerCast::UnsafeFnPointer => ( + "<safe-fn-pointer-to-unsafe-fn-pointer>", + "safe fn pointer to unsafe fn pointer", + ), PointerCast::ClosureFnPointer(Safety::Unsafe) => { - "<closure-to-unsafe-fn-pointer>" + ("<closure-to-unsafe-fn-pointer>", "closure to unsafe fn pointer") + } + PointerCast::ClosureFnPointer(Safety::Safe) => { + ("<closure-to-fn-pointer>", "closure to fn pointer") + } + PointerCast::MutToConstPointer => { + ("<mut-ptr-to-const-ptr>", "mut ptr to const ptr") } - PointerCast::ClosureFnPointer(Safety::Safe) => "<closure-to-fn-pointer>", - PointerCast::MutToConstPointer => "<mut-ptr-to-const-ptr>", - PointerCast::ArrayToPointer => "<array-ptr-to-element-ptr>", - PointerCast::Unsize => "<unsize>", + PointerCast::ArrayToPointer => ("<array-ptr-to-element-ptr>", ""), + PointerCast::Unsize => ("<unsize>", "unsize"), } } _ => continue, }; acc.push(InlayHint { range: expr.syntax().text_range(), - kind: if postfix { - InlayKind::AdjustmentHintPostfix - } else { - InlayKind::AdjustmentHint - }, - label: if postfix { format!(".{}", text.trim_end()).into() } else { text.into() }, - tooltip: None, + kind: if postfix { InlayKind::AdjustmentPostfix } else { InlayKind::Adjustment }, + label: InlayHintLabel::simple( + if postfix { format!(".{}", text.trim_end()) } else { text.to_owned() }, + Some(InlayTooltip::Markdown(format!( + "`{}` → `{}` ({coercion} coercion)", + source.display(sema.db), + target.display(sema.db), + ))), + None, + ), }); } if !postfix && needs_inner_parens { - acc.push(InlayHint { - range: expr.syntax().text_range(), - kind: InlayKind::OpeningParenthesis, - label: "(".into(), - tooltip: None, - }); - acc.push(InlayHint { - range: expr.syntax().text_range(), - kind: InlayKind::ClosingParenthesis, - label: ")".into(), - tooltip: None, - }); + acc.push(InlayHint::opening_paren(expr.syntax().text_range())); + acc.push(InlayHint::closing_paren(expr.syntax().text_range())); } if needs_outer_parens { - acc.push(InlayHint { - range: expr.syntax().text_range(), - kind: InlayKind::ClosingParenthesis, - label: ")".into(), - tooltip: None, - }); + acc.push(InlayHint::closing_paren(expr.syntax().text_range())); } Some(()) } @@ -223,16 +211,21 @@ fn needs_parens_for_adjustment_hints(expr: &ast::Expr, postfix: bool) -> (bool, ted::replace(expr.syntax(), dummy_expr.syntax()); let parent = dummy_expr.syntax().parent(); - let expr = if postfix { - let ast::Expr::TryExpr(e) = &dummy_expr else { unreachable!() }; - let Some(ast::Expr::ParenExpr(e)) = e.expr() else { unreachable!() }; + let Some(expr) = (|| { + if postfix { + let ast::Expr::TryExpr(e) = &dummy_expr else { return None }; + let Some(ast::Expr::ParenExpr(e)) = e.expr() else { return None }; - e.expr().unwrap() - } else { - let ast::Expr::RefExpr(e) = &dummy_expr else { unreachable!() }; - let Some(ast::Expr::ParenExpr(e)) = e.expr() else { unreachable!() }; + e.expr() + } else { + let ast::Expr::RefExpr(e) = &dummy_expr else { return None }; + let Some(ast::Expr::ParenExpr(e)) = e.expr() else { return None }; - e.expr().unwrap() + e.expr() + } + })() else { + never!("broken syntax tree?\n{:?}\n{:?}", expr, dummy_expr); + return (true, true) }; // At this point diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs index adec19c76..4af7f9bdb 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs @@ -12,9 +12,7 @@ use syntax::{ match_ast, }; -use crate::{ - inlay_hints::closure_has_block_body, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip, -}; +use crate::{inlay_hints::closure_has_block_body, InlayHint, InlayHintsConfig, InlayKind}; use super::label_of_ty; @@ -22,7 +20,7 @@ pub(super) fn hints( acc: &mut Vec<InlayHint>, famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - file_id: FileId, + _file_id: FileId, pat: &ast::IdentPat, ) -> Option<()> { if !config.type_hints { @@ -50,12 +48,8 @@ pub(super) fn hints( Some(name) => name.syntax().text_range(), None => pat.syntax().text_range(), }, - kind: InlayKind::TypeHint, + kind: InlayKind::Type, label, - tooltip: pat - .name() - .map(|it| it.syntax().text_range()) - .map(|it| InlayTooltip::HoverRanged(file_id, it)), }); Some(()) @@ -73,28 +67,23 @@ fn should_not_display_type_hint( return true; } - if let Some(hir::Adt::Struct(s)) = pat_ty.as_adt() { - if s.fields(db).is_empty() && s.name(db).to_smol_str() == bind_pat.to_string() { - return true; - } - } - - if config.hide_closure_initialization_hints { - if let Some(parent) = bind_pat.syntax().parent() { - if let Some(it) = ast::LetStmt::cast(parent) { - if let Some(ast::Expr::ClosureExpr(closure)) = it.initializer() { - if closure_has_block_body(&closure) { - return true; - } - } - } - } + if sema.resolve_bind_pat_to_const(bind_pat).is_some() { + return true; } for node in bind_pat.syntax().ancestors() { match_ast! { match node { - ast::LetStmt(it) => return it.ty().is_some(), + ast::LetStmt(it) => { + if config.hide_closure_initialization_hints { + if let Some(ast::Expr::ClosureExpr(closure)) = it.initializer() { + if closure_has_block_body(&closure) { + return true; + } + } + } + return it.ty().is_some() + }, // FIXME: We might wanna show type hints in parameters for non-top level patterns as well ast::Param(it) => return it.ty().is_some(), ast::MatchArm(_) => return pat_is_enum_variant(db, bind_pat, pat_ty), @@ -194,8 +183,7 @@ mod tests { use crate::{fixture, inlay_hints::InlayHintsConfig}; use crate::inlay_hints::tests::{ - check, check_expect, check_with_config, DISABLED_CONFIG, DISABLED_CONFIG_WITH_LINKS, - TEST_CONFIG, + check, check_expect, check_with_config, DISABLED_CONFIG, TEST_CONFIG, }; use crate::ClosureReturnTypeHints; @@ -291,7 +279,7 @@ fn main() { fn iterator_hint_regression_issue_12674() { // Ensure we don't crash while solving the projection type of iterators. check_expect( - InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG_WITH_LINKS }, + InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, r#" //- minicore: iterators struct S<T>(T); @@ -322,22 +310,66 @@ fn main(a: SliceIter<'_, Container>) { [ InlayHint { range: 484..554, - kind: ChainingHint, + kind: Chaining, label: [ - "impl Iterator<Item = impl Iterator<Item = &&str>>", - ], - tooltip: Some( - HoverRanged( - FileId( - 0, + "impl ", + InlayHintLabelPart { + text: "Iterator", + linked_location: Some( + FileRange { + file_id: FileId( + 1, + ), + range: 2611..2619, + }, + ), + tooltip: "", + }, + "<", + InlayHintLabelPart { + text: "Item", + linked_location: Some( + FileRange { + file_id: FileId( + 1, + ), + range: 2643..2647, + }, + ), + tooltip: "", + }, + " = impl ", + InlayHintLabelPart { + text: "Iterator", + linked_location: Some( + FileRange { + file_id: FileId( + 1, + ), + range: 2611..2619, + }, + ), + tooltip: "", + }, + "<", + InlayHintLabelPart { + text: "Item", + linked_location: Some( + FileRange { + file_id: FileId( + 1, + ), + range: 2643..2647, + }, ), - 484..554, - ), - ), + tooltip: "", + }, + " = &&str>>", + ], }, InlayHint { range: 484..485, - kind: ChainingHint, + kind: Chaining, label: [ "", InlayHintLabelPart { @@ -350,6 +382,7 @@ fn main(a: SliceIter<'_, Container>) { range: 289..298, }, ), + tooltip: "", }, "<", InlayHintLabelPart { @@ -362,17 +395,10 @@ fn main(a: SliceIter<'_, Container>) { range: 238..247, }, ), + tooltip: "", }, ">", ], - tooltip: Some( - HoverRanged( - FileId( - 0, - ), - 484..485, - ), - ), }, ] "#]], @@ -537,6 +563,21 @@ fn main() { } #[test] + fn const_pats_have_no_type_hints() { + check_types( + r#" +const FOO: usize = 0; + +fn main() { + match 0 { + FOO => (), + _ => () + } +}"#, + ); + } + + #[test] fn let_statement() { check_types( r#" diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs index a0166d004..5d9729263 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs @@ -7,7 +7,7 @@ use ide_db::RootDatabase; use syntax::ast::{self, AstNode}; -use crate::{InlayHint, InlayHintsConfig, InlayKind, InlayTooltip}; +use crate::{InlayHint, InlayHintsConfig, InlayKind}; pub(super) fn hints( acc: &mut Vec<InlayHint>, @@ -29,8 +29,17 @@ pub(super) fn hints( _ => None, }) .last(); - let range = - outer_paren_pat.as_ref().map_or_else(|| pat.syntax(), |it| it.syntax()).text_range(); + let range = outer_paren_pat.as_ref().map_or_else( + || match pat { + // for ident patterns that @ bind a name, render the un-ref patterns in front of the inner pattern + // instead of the name as that makes it more clear and doesn't really change the outcome + ast::Pat::IdentPat(it) => { + it.pat().map_or_else(|| it.syntax().text_range(), |it| it.syntax().text_range()) + } + it => it.syntax().text_range(), + }, + |it| it.syntax().text_range(), + ); let pattern_adjustments = sema.pattern_adjustments(pat); pattern_adjustments.iter().for_each(|ty| { let reference = ty.is_reference(); @@ -40,12 +49,7 @@ pub(super) fn hints( (true, false) => "&", _ => return, }; - acc.push(InlayHint { - range, - kind: InlayKind::BindingModeHint, - label: r.to_string().into(), - tooltip: Some(InlayTooltip::String("Inferred binding mode".into())), - }); + acc.push(InlayHint { range, kind: InlayKind::BindingMode, label: r.to_string().into() }); }); match pat { ast::Pat::IdentPat(pat) if pat.ref_token().is_none() && pat.mut_token().is_none() => { @@ -57,24 +61,13 @@ pub(super) fn hints( }; acc.push(InlayHint { range: pat.syntax().text_range(), - kind: InlayKind::BindingModeHint, + kind: InlayKind::BindingMode, label: bm.to_string().into(), - tooltip: Some(InlayTooltip::String("Inferred binding mode".into())), }); } ast::Pat::OrPat(pat) if !pattern_adjustments.is_empty() && outer_paren_pat.is_none() => { - acc.push(InlayHint { - range: pat.syntax().text_range(), - kind: InlayKind::OpeningParenthesis, - label: "(".into(), - tooltip: None, - }); - acc.push(InlayHint { - range: pat.syntax().text_range(), - kind: InlayKind::ClosingParenthesis, - label: ")".into(), - tooltip: None, - }); + acc.push(InlayHint::opening_paren(pat.syntax().text_range())); + acc.push(InlayHint::closing_paren(pat.syntax().text_range())); } _ => (), } @@ -139,4 +132,20 @@ fn __( }"#, ); } + + #[test] + fn hints_binding_modes_complex_ident_pat() { + check_with_config( + InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG }, + r#" +struct Struct { + field: &'static str, +} +fn foo(s @ Struct { field, .. }: &Struct) {} + //^^^^^^^^^^^^^^^^^^^^^^^^ref + //^^^^^^^^^^^^^^^^^^^^& + //^^^^^ref +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs index 8810d5d34..0c54f084c 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs @@ -5,7 +5,7 @@ use syntax::{ Direction, NodeOrToken, SyntaxKind, T, }; -use crate::{FileId, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip}; +use crate::{FileId, InlayHint, InlayHintsConfig, InlayKind}; use super::label_of_ty; @@ -13,7 +13,7 @@ pub(super) fn hints( acc: &mut Vec<InlayHint>, famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - file_id: FileId, + _file_id: FileId, expr: &ast::Expr, ) -> Option<()> { if !config.chaining_hints { @@ -59,9 +59,8 @@ pub(super) fn hints( } acc.push(InlayHint { range: expr.syntax().text_range(), - kind: InlayKind::ChainingHint, + kind: InlayKind::Chaining, label: label_of_ty(famous_defs, config, ty)?, - tooltip: Some(InlayTooltip::HoverRanged(file_id, expr.syntax().text_range())), }); } } @@ -73,10 +72,7 @@ mod tests { use expect_test::expect; use crate::{ - inlay_hints::tests::{ - check_expect, check_with_config, DISABLED_CONFIG, DISABLED_CONFIG_WITH_LINKS, - TEST_CONFIG, - }, + inlay_hints::tests::{check_expect, check_with_config, DISABLED_CONFIG, TEST_CONFIG}, InlayHintsConfig, }; @@ -88,11 +84,7 @@ mod tests { #[test] fn chaining_hints_ignore_comments() { check_expect( - InlayHintsConfig { - type_hints: false, - chaining_hints: true, - ..DISABLED_CONFIG_WITH_LINKS - }, + InlayHintsConfig { type_hints: false, chaining_hints: true, ..DISABLED_CONFIG }, r#" struct A(B); impl A { fn into_b(self) -> B { self.0 } } @@ -111,7 +103,7 @@ fn main() { [ InlayHint { range: 147..172, - kind: ChainingHint, + kind: Chaining, label: [ "", InlayHintLabelPart { @@ -124,21 +116,14 @@ fn main() { range: 63..64, }, ), + tooltip: "", }, "", ], - tooltip: Some( - HoverRanged( - FileId( - 0, - ), - 147..172, - ), - ), }, InlayHint { range: 147..154, - kind: ChainingHint, + kind: Chaining, label: [ "", InlayHintLabelPart { @@ -151,17 +136,10 @@ fn main() { range: 7..8, }, ), + tooltip: "", }, "", ], - tooltip: Some( - HoverRanged( - FileId( - 0, - ), - 147..154, - ), - ), }, ] "#]], @@ -210,33 +188,43 @@ fn main() { [ InlayHint { range: 143..190, - kind: ChainingHint, + kind: Chaining, label: [ - "C", - ], - tooltip: Some( - HoverRanged( - FileId( - 0, + "", + InlayHintLabelPart { + text: "C", + linked_location: Some( + FileRange { + file_id: FileId( + 0, + ), + range: 51..52, + }, ), - 143..190, - ), - ), + tooltip: "", + }, + "", + ], }, InlayHint { range: 143..179, - kind: ChainingHint, + kind: Chaining, label: [ - "B", - ], - tooltip: Some( - HoverRanged( - FileId( - 0, + "", + InlayHintLabelPart { + text: "B", + linked_location: Some( + FileRange { + file_id: FileId( + 0, + ), + range: 29..30, + }, ), - 143..179, - ), - ), + tooltip: "", + }, + "", + ], }, ] "#]], @@ -246,7 +234,7 @@ fn main() { #[test] fn struct_access_chaining_hints() { check_expect( - InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG_WITH_LINKS }, + InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, r#" struct A { pub b: B } struct B { pub c: C } @@ -269,7 +257,7 @@ fn main() { [ InlayHint { range: 143..190, - kind: ChainingHint, + kind: Chaining, label: [ "", InlayHintLabelPart { @@ -282,21 +270,14 @@ fn main() { range: 51..52, }, ), + tooltip: "", }, "", ], - tooltip: Some( - HoverRanged( - FileId( - 0, - ), - 143..190, - ), - ), }, InlayHint { range: 143..179, - kind: ChainingHint, + kind: Chaining, label: [ "", InlayHintLabelPart { @@ -309,17 +290,10 @@ fn main() { range: 29..30, }, ), + tooltip: "", }, "", ], - tooltip: Some( - HoverRanged( - FileId( - 0, - ), - 143..179, - ), - ), }, ] "#]], @@ -329,7 +303,7 @@ fn main() { #[test] fn generic_chaining_hints() { check_expect( - InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG_WITH_LINKS }, + InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, r#" struct A<T>(T); struct B<T>(T); @@ -353,7 +327,7 @@ fn main() { [ InlayHint { range: 246..283, - kind: ChainingHint, + kind: Chaining, label: [ "", InlayHintLabelPart { @@ -366,6 +340,7 @@ fn main() { range: 23..24, }, ), + tooltip: "", }, "<", InlayHintLabelPart { @@ -378,21 +353,14 @@ fn main() { range: 55..56, }, ), + tooltip: "", }, "<i32, bool>>", ], - tooltip: Some( - HoverRanged( - FileId( - 0, - ), - 246..283, - ), - ), }, InlayHint { range: 246..265, - kind: ChainingHint, + kind: Chaining, label: [ "", InlayHintLabelPart { @@ -405,6 +373,7 @@ fn main() { range: 7..8, }, ), + tooltip: "", }, "<", InlayHintLabelPart { @@ -417,17 +386,10 @@ fn main() { range: 55..56, }, ), + tooltip: "", }, "<i32, bool>>", ], - tooltip: Some( - HoverRanged( - FileId( - 0, - ), - 246..265, - ), - ), }, ] "#]], @@ -437,7 +399,7 @@ fn main() { #[test] fn shorten_iterator_chaining_hints() { check_expect( - InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG_WITH_LINKS }, + InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, r#" //- minicore: iterators use core::iter; @@ -463,52 +425,106 @@ fn main() { [ InlayHint { range: 174..241, - kind: ChainingHint, + kind: Chaining, label: [ - "impl Iterator<Item = ()>", - ], - tooltip: Some( - HoverRanged( - FileId( - 0, + "impl ", + InlayHintLabelPart { + text: "Iterator", + linked_location: Some( + FileRange { + file_id: FileId( + 1, + ), + range: 2611..2619, + }, + ), + tooltip: "", + }, + "<", + InlayHintLabelPart { + text: "Item", + linked_location: Some( + FileRange { + file_id: FileId( + 1, + ), + range: 2643..2647, + }, ), - 174..241, - ), - ), + tooltip: "", + }, + " = ()>", + ], }, InlayHint { range: 174..224, - kind: ChainingHint, + kind: Chaining, label: [ - "impl Iterator<Item = ()>", - ], - tooltip: Some( - HoverRanged( - FileId( - 0, + "impl ", + InlayHintLabelPart { + text: "Iterator", + linked_location: Some( + FileRange { + file_id: FileId( + 1, + ), + range: 2611..2619, + }, ), - 174..224, - ), - ), + tooltip: "", + }, + "<", + InlayHintLabelPart { + text: "Item", + linked_location: Some( + FileRange { + file_id: FileId( + 1, + ), + range: 2643..2647, + }, + ), + tooltip: "", + }, + " = ()>", + ], }, InlayHint { range: 174..206, - kind: ChainingHint, + kind: Chaining, label: [ - "impl Iterator<Item = ()>", - ], - tooltip: Some( - HoverRanged( - FileId( - 0, + "impl ", + InlayHintLabelPart { + text: "Iterator", + linked_location: Some( + FileRange { + file_id: FileId( + 1, + ), + range: 2611..2619, + }, ), - 174..206, - ), - ), + tooltip: "", + }, + "<", + InlayHintLabelPart { + text: "Item", + linked_location: Some( + FileRange { + file_id: FileId( + 1, + ), + range: 2643..2647, + }, + ), + tooltip: "", + }, + " = ()>", + ], }, InlayHint { range: 174..189, - kind: ChainingHint, + kind: Chaining, label: [ "&mut ", InlayHintLabelPart { @@ -521,17 +537,10 @@ fn main() { range: 24..30, }, ), + tooltip: "", }, "", ], - tooltip: Some( - HoverRanged( - FileId( - 0, - ), - 174..189, - ), - ), }, ] "#]], @@ -564,7 +573,7 @@ fn main() { [ InlayHint { range: 124..130, - kind: TypeHint, + kind: Type, label: [ "", InlayHintLabelPart { @@ -577,21 +586,14 @@ fn main() { range: 7..13, }, ), + tooltip: "", }, "", ], - tooltip: Some( - HoverRanged( - FileId( - 0, - ), - 124..130, - ), - ), }, InlayHint { range: 145..185, - kind: ChainingHint, + kind: Chaining, label: [ "", InlayHintLabelPart { @@ -604,21 +606,14 @@ fn main() { range: 7..13, }, ), + tooltip: "", }, "", ], - tooltip: Some( - HoverRanged( - FileId( - 0, - ), - 145..185, - ), - ), }, InlayHint { range: 145..168, - kind: ChainingHint, + kind: Chaining, label: [ "", InlayHintLabelPart { @@ -631,32 +626,28 @@ fn main() { range: 7..13, }, ), + tooltip: "", }, "", ], - tooltip: Some( - HoverRanged( - FileId( - 0, - ), - 145..168, - ), - ), }, InlayHint { range: 222..228, - kind: ParameterHint, + kind: Parameter, label: [ - "self", - ], - tooltip: Some( - HoverOffset( - FileId( - 0, + InlayHintLabelPart { + text: "self", + linked_location: Some( + FileRange { + file_id: FileId( + 0, + ), + range: 42..46, + }, ), - 42, - ), - ), + tooltip: "", + }, + ], }, ] "#]], diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs index e340c64c5..14c11be54 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs @@ -10,9 +10,7 @@ use syntax::{ match_ast, SyntaxKind, SyntaxNode, T, }; -use crate::{ - inlay_hints::InlayHintLabelPart, FileId, InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind, -}; +use crate::{FileId, InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind}; pub(super) fn hints( acc: &mut Vec<InlayHint>, @@ -109,15 +107,11 @@ pub(super) fn hints( return None; } - let linked_location = config - .location_links - .then(|| name_range.map(|range| FileRange { file_id, range })) - .flatten(); + let linked_location = name_range.map(|range| FileRange { file_id, range }); acc.push(InlayHint { range: closing_token.text_range(), - kind: InlayKind::ClosingBraceHint, - label: InlayHintLabel { parts: vec![InlayHintLabelPart { text: label, linked_location }] }, - tooltip: None, // provided by label part location + kind: InlayKind::ClosingBrace, + label: InlayHintLabel::simple(label, None, linked_location), }); None diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs index d9929beaa..f03a18b8e 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs @@ -4,7 +4,7 @@ use syntax::ast::{self, AstNode}; use crate::{ inlay_hints::closure_has_block_body, ClosureReturnTypeHints, InlayHint, InlayHintsConfig, - InlayKind, InlayTooltip, + InlayKind, }; use super::label_of_ty; @@ -13,7 +13,7 @@ pub(super) fn hints( acc: &mut Vec<InlayHint>, famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, - file_id: FileId, + _file_id: FileId, closure: ast::ClosureExpr, ) -> Option<()> { if config.closure_return_type_hints == ClosureReturnTypeHints::Never { @@ -41,9 +41,8 @@ pub(super) fn hints( } acc.push(InlayHint { range: param_list.syntax().text_range(), - kind: InlayKind::ClosureReturnTypeHint, + kind: InlayKind::ClosureReturnType, label: label_of_ty(famous_defs, config, ty)?, - tooltip: Some(InlayTooltip::HoverRanged(file_id, param_list.syntax().text_range())), }); Some(()) } diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs index f32c4bdf2..5dd51ad11 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs @@ -4,27 +4,43 @@ //! Bar/* = 0*/, //! } //! ``` -use ide_db::{base_db::FileId, famous_defs::FamousDefs}; +use hir::Semantics; +use ide_db::{base_db::FileId, famous_defs::FamousDefs, RootDatabase}; use syntax::ast::{self, AstNode, HasName}; -use crate::{DiscriminantHints, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip}; +use crate::{ + DiscriminantHints, InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind, InlayTooltip, +}; -pub(super) fn hints( +pub(super) fn enum_hints( acc: &mut Vec<InlayHint>, FamousDefs(sema, _): &FamousDefs<'_, '_>, config: &InlayHintsConfig, _: FileId, - variant: &ast::Variant, + enum_: ast::Enum, ) -> Option<()> { - let field_list = match config.discriminant_hints { - DiscriminantHints::Always => variant.field_list(), - DiscriminantHints::Fieldless => match variant.field_list() { - Some(_) => return None, - None => None, - }, - DiscriminantHints::Never => return None, + let enabled = match config.discriminant_hints { + DiscriminantHints::Always => true, + DiscriminantHints::Fieldless => { + !sema.to_def(&enum_)?.is_data_carrying(sema.db) + || enum_.variant_list()?.variants().any(|v| v.expr().is_some()) + } + DiscriminantHints::Never => false, }; + if !enabled { + return None; + } + for variant in enum_.variant_list()?.variants() { + variant_hints(acc, sema, &variant); + } + None +} +fn variant_hints( + acc: &mut Vec<InlayHint>, + sema: &Semantics<'_, RootDatabase>, + variant: &ast::Variant, +) -> Option<()> { if variant.eq_token().is_some() { return None; } @@ -37,19 +53,22 @@ pub(super) fn hints( let d = v.eval(sema.db); acc.push(InlayHint { - range: match field_list { + range: match variant.field_list() { Some(field_list) => name.syntax().text_range().cover(field_list.syntax().text_range()), None => name.syntax().text_range(), }, - kind: InlayKind::DiscriminantHint, - label: match &d { - Ok(v) => format!("{}", v).into(), - Err(_) => "?".into(), - }, - tooltip: Some(InlayTooltip::String(match &d { - Ok(_) => "enum variant discriminant".into(), - Err(e) => format!("{e:?}").into(), - })), + kind: InlayKind::Discriminant, + label: InlayHintLabel::simple( + match &d { + Ok(v) => format!("{}", v), + Err(_) => "?".into(), + }, + Some(InlayTooltip::String(match &d { + Ok(_) => "enum variant discriminant".into(), + Err(e) => format!("{e:?}").into(), + })), + None, + ), }); Some(()) @@ -86,15 +105,30 @@ mod tests { check_discriminants( r#" enum Enum { - Variant, - //^^^^^^^0 - Variant1, - //^^^^^^^^1 - Variant2, - //^^^^^^^^2 - Variant5 = 5, - Variant6, - //^^^^^^^^6 + Variant, +//^^^^^^^0 + Variant1, +//^^^^^^^^1 + Variant2, +//^^^^^^^^2 + Variant5 = 5, + Variant6, +//^^^^^^^^6 +} +"#, + ); + check_discriminants_fieldless( + r#" +enum Enum { + Variant, +//^^^^^^^0 + Variant1, +//^^^^^^^^1 + Variant2, +//^^^^^^^^2 + Variant5 = 5, + Variant6, +//^^^^^^^^6 } "#, ); @@ -128,8 +162,22 @@ enum Enum { enum Enum { Variant(), Variant1, + Variant2 {}, + Variant3, + Variant5, + Variant6, +} +"#, + ); + check_discriminants_fieldless( + r#" +enum Enum { + Variant(), + //^^^^^^^^^0 + Variant1, //^^^^^^^^1 Variant2 {}, + //^^^^^^^^^^^2 Variant3, //^^^^^^^^3 Variant5 = 5, diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs index 2aa5e3dc7..b7182085b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs @@ -10,7 +10,7 @@ use syntax::{ SyntaxToken, }; -use crate::{InlayHint, InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints}; +use crate::{InlayHint, InlayHintsConfig, InlayKind, LifetimeElisionHints}; pub(super) fn hints( acc: &mut Vec<InlayHint>, @@ -23,9 +23,8 @@ pub(super) fn hints( let mk_lt_hint = |t: SyntaxToken, label: String| InlayHint { range: t.text_range(), - kind: InlayKind::LifetimeHint, + kind: InlayKind::Lifetime, label: label.into(), - tooltip: Some(InlayTooltip::String("Elided lifetime".into())), }; let param_list = func.param_list()?; @@ -183,21 +182,19 @@ pub(super) fn hints( let is_empty = gpl.generic_params().next().is_none(); acc.push(InlayHint { range: angle_tok.text_range(), - kind: InlayKind::LifetimeHint, + kind: InlayKind::Lifetime, label: format!( "{}{}", allocated_lifetimes.iter().format(", "), if is_empty { "" } else { ", " } ) .into(), - tooltip: Some(InlayTooltip::String("Elided lifetimes".into())), }); } (None, allocated_lifetimes) => acc.push(InlayHint { range: func.name()?.syntax().text_range(), - kind: InlayKind::GenericParamListHint, + kind: InlayKind::GenericParamList, label: format!("<{}>", allocated_lifetimes.iter().format(", "),).into(), - tooltip: Some(InlayTooltip::String("Elided lifetimes".into())), }), } Some(()) diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs index 588a0e3b6..1122ee2e3 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs @@ -8,7 +8,7 @@ use syntax::{ SyntaxKind, }; -use crate::{InlayHint, InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints}; +use crate::{InlayHint, InlayHintsConfig, InlayKind, LifetimeElisionHints}; pub(super) fn hints( acc: &mut Vec<InlayHint>, @@ -32,9 +32,8 @@ pub(super) fn hints( let t = ty.amp_token()?; acc.push(InlayHint { range: t.text_range(), - kind: InlayKind::LifetimeHint, + kind: InlayKind::Lifetime, label: "'static".to_owned().into(), - tooltip: Some(InlayTooltip::String("Elided static lifetime".into())), }); } } diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs index ecee67632..9cdae6324 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs @@ -10,7 +10,7 @@ use ide_db::{base_db::FileRange, RootDatabase}; use stdx::to_lower_snake_case; use syntax::ast::{self, AstNode, HasArgList, HasName, UnaryOp}; -use crate::{InlayHint, InlayHintsConfig, InlayKind, InlayTooltip}; +use crate::{InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind}; pub(super) fn hints( acc: &mut Vec<InlayHint>, @@ -43,21 +43,20 @@ pub(super) fn hints( !should_hide_param_name_hint(sema, &callable, param_name, arg) }) .map(|(param, param_name, _, FileRange { range, .. })| { - let mut tooltip = None; + let mut linked_location = None; if let Some(name) = param { if let hir::CallableKind::Function(f) = callable.kind() { // assert the file is cached so we can map out of macros if let Some(_) = sema.source(f) { - tooltip = sema.original_range_opt(name.syntax()); + linked_location = sema.original_range_opt(name.syntax()); } } } InlayHint { range, - kind: InlayKind::ParameterHint, - label: param_name.into(), - tooltip: tooltip.map(|it| InlayTooltip::HoverOffset(it.file_id, it.range.start())), + kind: InlayKind::Parameter, + label: InlayHintLabel::simple(param_name, None, linked_location), } }); diff --git a/src/tools/rust-analyzer/crates/ide/src/join_lines.rs b/src/tools/rust-analyzer/crates/ide/src/join_lines.rs index edc48e84d..1cfde2362 100644 --- a/src/tools/rust-analyzer/crates/ide/src/join_lines.rs +++ b/src/tools/rust-analyzer/crates/ide/src/join_lines.rs @@ -161,10 +161,8 @@ fn remove_newline( } } - if config.join_assignments { - if join_assignments(edit, &prev, &next).is_some() { - return; - } + if config.join_assignments && join_assignments(edit, &prev, &next).is_some() { + return; } if config.unwrap_trivial_blocks { diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index 239456cb2..f2b535bdc 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -82,7 +82,8 @@ pub use crate::{ hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult}, inlay_hints::{ AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, InlayHint, - InlayHintLabel, InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints, + InlayHintLabel, InlayHintLabelPart, InlayHintsConfig, InlayKind, InlayTooltip, + LifetimeElisionHints, }, join_lines::JoinLinesConfig, markup::Markup, @@ -114,7 +115,7 @@ pub use ide_db::{ SourceRoot, SourceRootId, }, label::Label, - line_index::{LineCol, LineColUtf16, LineIndex}, + line_index::{LineCol, LineIndex}, search::{ReferenceCategory, SearchScope}, source_change::{FileSystemEdit, SourceChange}, symbol_index::Query, @@ -236,7 +237,7 @@ impl Analysis { Ok(Vec::new()), false, CrateOrigin::CratesIo { repo: None, name: None }, - None, + Err("Analysis::from_single_file has no target layout".into()), ); change.change_file(file_id, Some(Arc::new(text))); change.set_crate_graph(crate_graph); diff --git a/src/tools/rust-analyzer/crates/ide/src/markdown_remove.rs b/src/tools/rust-analyzer/crates/ide/src/markdown_remove.rs index 3ec5c629e..718868c87 100644 --- a/src/tools/rust-analyzer/crates/ide/src/markdown_remove.rs +++ b/src/tools/rust-analyzer/crates/ide/src/markdown_remove.rs @@ -11,12 +11,146 @@ pub(crate) fn remove_markdown(markdown: &str) -> String { for event in parser { match event { Event::Text(text) | Event::Code(text) => out.push_str(&text), - Event::SoftBreak | Event::HardBreak | Event::Rule | Event::End(Tag::CodeBlock(_)) => { - out.push('\n') + Event::SoftBreak => out.push(' '), + Event::HardBreak | Event::Rule | Event::End(Tag::CodeBlock(_)) => out.push('\n'), + Event::End(Tag::Paragraph) => { + out.push('\n'); + out.push('\n'); } - _ => {} + Event::Start(_) + | Event::End(_) + | Event::Html(_) + | Event::FootnoteReference(_) + | Event::TaskListMarker(_) => (), } } + if let Some(p) = out.rfind(|c| c != '\n') { + out.drain(p + 1..); + } + out } + +#[cfg(test)] +mod tests { + use expect_test::expect; + + use super::*; + + #[test] + fn smoke_test() { + let res = remove_markdown( + r##" +A function or function pointer. + +Functions are the primary way code is executed within Rust. Function blocks, usually just +called functions, can be defined in a variety of different places and be assigned many +different attributes and modifiers. + +Standalone functions that just sit within a module not attached to anything else are common, +but most functions will end up being inside [`impl`] blocks, either on another type itself, or +as a trait impl for that type. + +```rust +fn standalone_function() { + // code +} + +pub fn public_thing(argument: bool) -> String { + // code + # "".to_string() +} + +struct Thing { + foo: i32, +} + +impl Thing { + pub fn new() -> Self { + Self { + foo: 42, + } + } +} +``` + +In addition to presenting fixed types in the form of `fn name(arg: type, ..) -> return_type`, +functions can also declare a list of type parameters along with trait bounds that they fall +into. + +```rust +fn generic_function<T: Clone>(x: T) -> (T, T, T) { + (x.clone(), x.clone(), x.clone()) +} + +fn generic_where<T>(x: T) -> T + where T: std::ops::Add<Output = T> + Copy +{ + x + x + x +} +``` + +Declaring trait bounds in the angle brackets is functionally identical to using a `where` +clause. It's up to the programmer to decide which works better in each situation, but `where` +tends to be better when things get longer than one line. + +Along with being made public via `pub`, `fn` can also have an [`extern`] added for use in +FFI. + +For more information on the various types of functions and how they're used, consult the [Rust +book] or the [Reference]. + +[`impl`]: keyword.impl.html +[`extern`]: keyword.extern.html +[Rust book]: ../book/ch03-03-how-functions-work.html +[Reference]: ../reference/items/functions.html +"##, + ); + expect![[r#" + A function or function pointer. + + Functions are the primary way code is executed within Rust. Function blocks, usually just called functions, can be defined in a variety of different places and be assigned many different attributes and modifiers. + + Standalone functions that just sit within a module not attached to anything else are common, but most functions will end up being inside impl blocks, either on another type itself, or as a trait impl for that type. + + fn standalone_function() { + // code + } + + pub fn public_thing(argument: bool) -> String { + // code + # "".to_string() + } + + struct Thing { + foo: i32, + } + + impl Thing { + pub fn new() -> Self { + Self { + foo: 42, + } + } + } + + In addition to presenting fixed types in the form of fn name(arg: type, ..) -> return_type, functions can also declare a list of type parameters along with trait bounds that they fall into. + + fn generic_function<T: Clone>(x: T) -> (T, T, T) { + (x.clone(), x.clone(), x.clone()) + } + + fn generic_where<T>(x: T) -> T + where T: std::ops::Add<Output = T> + Copy + { + x + x + x + } + + Declaring trait bounds in the angle brackets is functionally identical to using a where clause. It's up to the programmer to decide which works better in each situation, but where tends to be better when things get longer than one line. + + Along with being made public via pub, fn can also have an extern added for use in FFI. + + For more information on the various types of functions and how they're used, consult the Rust book or the Reference."#]].assert_eq(&res); + } +} diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs index 0f758cfa2..cabbc2872 100644 --- a/src/tools/rust-analyzer/crates/ide/src/references.rs +++ b/src/tools/rust-analyzer/crates/ide/src/references.rs @@ -1636,4 +1636,399 @@ pub fn deri$0ve(_stream: TokenStream) -> TokenStream {} "#]], ); } + + #[test] + fn assoc_items_trait_def() { + check( + r#" +trait Trait { + const CONST$0: usize; +} + +impl Trait for () { + const CONST: usize = 0; +} + +impl Trait for ((),) { + const CONST: usize = 0; +} + +fn f<T: Trait>() { + let _ = <()>::CONST; + + let _ = T::CONST; +} +"#, + expect![[r#" + CONST Const FileId(0) 18..37 24..29 + + FileId(0) 71..76 + FileId(0) 125..130 + FileId(0) 183..188 + FileId(0) 206..211 + "#]], + ); + check( + r#" +trait Trait { + type TypeAlias$0; +} + +impl Trait for () { + type TypeAlias = (); +} + +impl Trait for ((),) { + type TypeAlias = (); +} + +fn f<T: Trait>() { + let _: <() as Trait>::TypeAlias; + + let _: T::TypeAlias; +} +"#, + expect![[r#" + TypeAlias TypeAlias FileId(0) 18..33 23..32 + + FileId(0) 66..75 + FileId(0) 117..126 + FileId(0) 181..190 + FileId(0) 207..216 + "#]], + ); + check( + r#" +trait Trait { + fn function$0() {} +} + +impl Trait for () { + fn function() {} +} + +impl Trait for ((),) { + fn function() {} +} + +fn f<T: Trait>() { + let _ = <()>::function; + + let _ = T::function; +} +"#, + expect![[r#" + function Function FileId(0) 18..34 21..29 + + FileId(0) 65..73 + FileId(0) 112..120 + FileId(0) 166..174 + FileId(0) 192..200 + "#]], + ); + } + + #[test] + fn assoc_items_trait_impl_def() { + check( + r#" +trait Trait { + const CONST: usize; +} + +impl Trait for () { + const CONST$0: usize = 0; +} + +impl Trait for ((),) { + const CONST: usize = 0; +} + +fn f<T: Trait>() { + let _ = <()>::CONST; + + let _ = T::CONST; +} +"#, + expect![[r#" + CONST Const FileId(0) 65..88 71..76 + + FileId(0) 183..188 + "#]], + ); + check( + r#" +trait Trait { + type TypeAlias; +} + +impl Trait for () { + type TypeAlias$0 = (); +} + +impl Trait for ((),) { + type TypeAlias = (); +} + +fn f<T: Trait>() { + let _: <() as Trait>::TypeAlias; + + let _: T::TypeAlias; +} +"#, + expect![[r#" + TypeAlias TypeAlias FileId(0) 61..81 66..75 + + FileId(0) 23..32 + FileId(0) 117..126 + FileId(0) 181..190 + FileId(0) 207..216 + "#]], + ); + check( + r#" +trait Trait { + fn function() {} +} + +impl Trait for () { + fn function$0() {} +} + +impl Trait for ((),) { + fn function() {} +} + +fn f<T: Trait>() { + let _ = <()>::function; + + let _ = T::function; +} +"#, + expect![[r#" + function Function FileId(0) 62..78 65..73 + + FileId(0) 166..174 + "#]], + ); + } + + #[test] + fn assoc_items_ref() { + check( + r#" +trait Trait { + const CONST: usize; +} + +impl Trait for () { + const CONST: usize = 0; +} + +impl Trait for ((),) { + const CONST: usize = 0; +} + +fn f<T: Trait>() { + let _ = <()>::CONST$0; + + let _ = T::CONST; +} +"#, + expect![[r#" + CONST Const FileId(0) 65..88 71..76 + + FileId(0) 183..188 + "#]], + ); + check( + r#" +trait Trait { + type TypeAlias; +} + +impl Trait for () { + type TypeAlias = (); +} + +impl Trait for ((),) { + type TypeAlias = (); +} + +fn f<T: Trait>() { + let _: <() as Trait>::TypeAlias$0; + + let _: T::TypeAlias; +} +"#, + expect![[r#" + TypeAlias TypeAlias FileId(0) 18..33 23..32 + + FileId(0) 66..75 + FileId(0) 117..126 + FileId(0) 181..190 + FileId(0) 207..216 + "#]], + ); + check( + r#" +trait Trait { + fn function() {} +} + +impl Trait for () { + fn function() {} +} + +impl Trait for ((),) { + fn function() {} +} + +fn f<T: Trait>() { + let _ = <()>::function$0; + + let _ = T::function; +} +"#, + expect![[r#" + function Function FileId(0) 62..78 65..73 + + FileId(0) 166..174 + "#]], + ); + } + + #[test] + fn name_clashes() { + check( + r#" +trait Foo { + fn method$0(&self) -> u8; +} + +struct Bar { + method: u8, +} + +impl Foo for Bar { + fn method(&self) -> u8 { + self.method + } +} +fn method() {} +"#, + expect![[r#" + method Function FileId(0) 16..39 19..25 + + FileId(0) 101..107 + "#]], + ); + check( + r#" +trait Foo { + fn method(&self) -> u8; +} + +struct Bar { + method$0: u8, +} + +impl Foo for Bar { + fn method(&self) -> u8 { + self.method + } +} +fn method() {} +"#, + expect![[r#" + method Field FileId(0) 60..70 60..66 + + FileId(0) 136..142 Read + "#]], + ); + check( + r#" +trait Foo { + fn method(&self) -> u8; +} + +struct Bar { + method: u8, +} + +impl Foo for Bar { + fn method$0(&self) -> u8 { + self.method + } +} +fn method() {} +"#, + expect![[r#" + method Function FileId(0) 98..148 101..107 + + (no references) + "#]], + ); + check( + r#" +trait Foo { + fn method(&self) -> u8; +} + +struct Bar { + method: u8, +} + +impl Foo for Bar { + fn method(&self) -> u8 { + self.method$0 + } +} +fn method() {} +"#, + expect![[r#" + method Field FileId(0) 60..70 60..66 + + FileId(0) 136..142 Read + "#]], + ); + check( + r#" +trait Foo { + fn method(&self) -> u8; +} + +struct Bar { + method: u8, +} + +impl Foo for Bar { + fn method(&self) -> u8 { + self.method + } +} +fn method$0() {} +"#, + expect![[r#" + method Function FileId(0) 151..165 154..160 + + (no references) + "#]], + ); + } + + #[test] + fn raw_identifier() { + check( + r#" +fn r#fn$0() {} +fn main() { r#fn(); } +"#, + expect![[r#" + r#fn Function FileId(0) 0..12 3..7 + + FileId(0) 25..29 + "#]], + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide/src/rename.rs b/src/tools/rust-analyzer/crates/ide/src/rename.rs index 15bdf14fb..c0237e1ed 100644 --- a/src/tools/rust-analyzer/crates/ide/src/rename.rs +++ b/src/tools/rust-analyzer/crates/ide/src/rename.rs @@ -13,7 +13,7 @@ use ide_db::{ }; use itertools::Itertools; use stdx::{always, never}; -use syntax::{ast, AstNode, SyntaxNode}; +use syntax::{ast, utils::is_raw_identifier, AstNode, SmolStr, SyntaxNode, TextRange, TextSize}; use text_edit::TextEdit; @@ -48,7 +48,13 @@ pub(crate) fn prepare_rename( frange.range.contains_inclusive(position.offset) && frange.file_id == position.file_id ); - Ok(frange.range) + + Ok(match name_like { + ast::NameLike::Lifetime(_) => { + TextRange::new(frange.range.start() + TextSize::from(1), frange.range.end()) + } + _ => frange.range, + }) }) .reduce(|acc, cur| match (acc, cur) { // ensure all ranges are the same @@ -116,7 +122,11 @@ pub(crate) fn will_rename_file( let sema = Semantics::new(db); let module = sema.to_module_def(file_id)?; let def = Definition::Module(module); - let mut change = def.rename(&sema, new_name_stem).ok()?; + let mut change = if is_raw_identifier(new_name_stem) { + def.rename(&sema, &SmolStr::from_iter(["r#", new_name_stem])).ok()? + } else { + def.rename(&sema, new_name_stem).ok()? + }; change.file_system_edits.clear(); Some(change) } @@ -407,7 +417,7 @@ mod tests { #[test] fn test_prepare_rename_namelikes() { check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]); - check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"8..17: 'lifetime"#]]); + check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"9..17: lifetime"#]]); check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"23..27: name"#]]); } @@ -521,15 +531,19 @@ impl Foo { #[test] fn test_rename_to_invalid_identifier_lifetime2() { - cov_mark::check!(rename_not_a_lifetime_ident_ref); check( - "foo", + "_", r#"fn main<'a>(_: &'a$0 ()) {}"#, - "error: Invalid name `foo`: not a lifetime identifier", + r#"error: Invalid name `_`: not a lifetime identifier"#, ); } #[test] + fn test_rename_accepts_lifetime_without_apostrophe() { + check("foo", r#"fn main<'a>(_: &'a$0 ()) {}"#, r#"fn main<'foo>(_: &'foo ()) {}"#); + } + + #[test] fn test_rename_to_underscore_invalid() { cov_mark::check!(rename_underscore_multiple); check( @@ -549,6 +563,15 @@ impl Foo { } #[test] + fn test_rename_mod_invalid_raw_ident() { + check( + "r#self", + r#"mod foo$0 {}"#, + "error: Invalid name: `self` cannot be a raw identifier", + ); + } + + #[test] fn test_rename_for_local() { check( "k", @@ -1277,6 +1300,146 @@ mod bar$0; } #[test] + fn test_rename_mod_to_raw_ident() { + check_expect( + "r#fn", + r#" +//- /lib.rs +mod foo$0; + +fn main() { foo::bar::baz(); } + +//- /foo.rs +pub mod bar; + +//- /foo/bar.rs +pub fn baz() {} +"#, + expect![[r#" + SourceChange { + source_file_edits: { + FileId( + 0, + ): TextEdit { + indels: [ + Indel { + insert: "r#fn", + delete: 4..7, + }, + Indel { + insert: "r#fn", + delete: 22..25, + }, + ], + }, + }, + file_system_edits: [ + MoveFile { + src: FileId( + 1, + ), + dst: AnchoredPathBuf { + anchor: FileId( + 1, + ), + path: "fn.rs", + }, + }, + MoveDir { + src: AnchoredPathBuf { + anchor: FileId( + 1, + ), + path: "foo", + }, + src_id: FileId( + 1, + ), + dst: AnchoredPathBuf { + anchor: FileId( + 1, + ), + path: "fn", + }, + }, + ], + is_snippet: false, + } + "#]], + ); + } + + #[test] + fn test_rename_mod_from_raw_ident() { + check_expect( + "foo", + r#" +//- /lib.rs +mod r#fn$0; + +fn main() { r#fn::bar::baz(); } + +//- /fn.rs +pub mod bar; + +//- /fn/bar.rs +pub fn baz() {} +"#, + expect![[r#" + SourceChange { + source_file_edits: { + FileId( + 0, + ): TextEdit { + indels: [ + Indel { + insert: "foo", + delete: 4..8, + }, + Indel { + insert: "foo", + delete: 23..27, + }, + ], + }, + }, + file_system_edits: [ + MoveFile { + src: FileId( + 1, + ), + dst: AnchoredPathBuf { + anchor: FileId( + 1, + ), + path: "foo.rs", + }, + }, + MoveDir { + src: AnchoredPathBuf { + anchor: FileId( + 1, + ), + path: "fn", + }, + src_id: FileId( + 1, + ), + dst: AnchoredPathBuf { + anchor: FileId( + 1, + ), + path: "foo", + }, + }, + ], + is_snippet: false, + } + "#]], + ); + } + + #[test] fn test_enum_variant_from_module_1() { cov_mark::check!(rename_non_local); check( @@ -1832,6 +1995,31 @@ fn foo<'a>() -> &'a () { } #[test] + fn test_rename_label_new_name_without_apostrophe() { + check( + "foo", + r#" +fn main() { + 'outer$0: loop { + 'inner: loop { + break 'outer; + } + } +} + "#, + r#" +fn main() { + 'foo: loop { + 'inner: loop { + break 'foo; + } + } +} + "#, + ); + } + + #[test] fn test_self_to_self() { cov_mark::check!(rename_self_to_self); check( diff --git a/src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs b/src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs index ae539a5d3..e606072a8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs +++ b/src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs @@ -18,7 +18,9 @@ pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) { let crate_graph = db.crate_graph(); let mut shuffled_ids = crate_graph.iter().collect::<Vec<_>>(); - shuffle(&mut shuffled_ids); + + let mut rng = oorandom::Rand32::new(stdx::rand::seed()); + stdx::rand::shuffle(&mut shuffled_ids, |i| rng.rand_range(0..i as u32) as usize); let mut new_graph = CrateGraph::default(); @@ -52,21 +54,3 @@ pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) { db.set_crate_graph_with_durability(Arc::new(new_graph), Durability::HIGH); } - -fn shuffle<T>(slice: &mut [T]) { - let mut rng = oorandom::Rand32::new(seed()); - - let mut remaining = slice.len() - 1; - while remaining > 0 { - let index = rng.rand_range(0..remaining as u32); - slice.swap(remaining, index as usize); - remaining -= 1; - } -} - -fn seed() -> u64 { - use std::collections::hash_map::RandomState; - use std::hash::{BuildHasher, Hasher}; - - RandomState::new().build_hasher().finish() -} diff --git a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs index f807ba30f..f70ca55a5 100644 --- a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs +++ b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs @@ -4,8 +4,14 @@ use std::collections::BTreeSet; use either::Either; -use hir::{AssocItem, GenericParam, HasAttrs, HirDisplay, Semantics, Trait}; -use ide_db::{active_parameter::callable_for_node, base_db::FilePosition}; +use hir::{ + AssocItem, GenericParam, HasAttrs, HirDisplay, ModuleDef, PathResolution, Semantics, Trait, +}; +use ide_db::{ + active_parameter::{callable_for_node, generic_def_for_node}, + base_db::FilePosition, + FxIndexMap, +}; use stdx::format_to; use syntax::{ algo, @@ -37,14 +43,18 @@ impl SignatureHelp { } fn push_call_param(&mut self, param: &str) { - self.push_param('(', param); + self.push_param("(", param); } fn push_generic_param(&mut self, param: &str) { - self.push_param('<', param); + self.push_param("<", param); } - fn push_param(&mut self, opening_delim: char, param: &str) { + fn push_record_field(&mut self, param: &str) { + self.push_param("{ ", param); + } + + fn push_param(&mut self, opening_delim: &str, param: &str) { if !self.signature.ends_with(opening_delim) { self.signature.push_str(", "); } @@ -85,6 +95,13 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio } return signature_help_for_generics(&sema, garg_list, token); }, + ast::RecordExpr(record) => { + let cursor_outside = record.record_expr_field_list().and_then(|list| list.r_curly_token()).as_ref() == Some(&token); + if cursor_outside { + continue; + } + return signature_help_for_record_lit(&sema, record, token); + }, _ => (), } } @@ -92,8 +109,10 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio // Stop at multi-line expressions, since the signature of the outer call is not very // helpful inside them. if let Some(expr) = ast::Expr::cast(node.clone()) { - if expr.syntax().text().contains_char('\n') { - return None; + if !matches!(expr, ast::Expr::RecordExpr(..)) + && expr.syntax().text().contains_char('\n') + { + break; } } } @@ -107,18 +126,16 @@ fn signature_help_for_call( token: SyntaxToken, ) -> Option<SignatureHelp> { // Find the calling expression and its NameRef - let mut node = arg_list.syntax().parent()?; + let mut nodes = arg_list.syntax().ancestors().skip(1); let calling_node = loop { - if let Some(callable) = ast::CallableExpr::cast(node.clone()) { - if callable + if let Some(callable) = ast::CallableExpr::cast(nodes.next()?) { + let inside_callable = callable .arg_list() - .map_or(false, |it| it.syntax().text_range().contains(token.text_range().start())) - { + .map_or(false, |it| it.syntax().text_range().contains(token.text_range().start())); + if inside_callable { break callable; } } - - node = node.parent()?; }; let (callable, active_parameter) = callable_for_node(sema, &calling_node, &token)?; @@ -201,59 +218,11 @@ fn signature_help_for_call( fn signature_help_for_generics( sema: &Semantics<'_, RootDatabase>, - garg_list: ast::GenericArgList, + arg_list: ast::GenericArgList, token: SyntaxToken, ) -> Option<SignatureHelp> { - let arg_list = garg_list - .syntax() - .ancestors() - .filter_map(ast::GenericArgList::cast) - .find(|list| list.syntax().text_range().contains(token.text_range().start()))?; - - let mut active_parameter = arg_list - .generic_args() - .take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start()) - .count(); - - let first_arg_is_non_lifetime = arg_list - .generic_args() - .next() - .map_or(false, |arg| !matches!(arg, ast::GenericArg::LifetimeArg(_))); - - let mut generics_def = if let Some(path) = - arg_list.syntax().ancestors().find_map(ast::Path::cast) - { - let res = sema.resolve_path(&path)?; - let generic_def: hir::GenericDef = match res { - hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(), - hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(), - hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(), - hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(), - hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(), - hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_)) - | hir::PathResolution::Def(hir::ModuleDef::Const(_)) - | hir::PathResolution::Def(hir::ModuleDef::Macro(_)) - | hir::PathResolution::Def(hir::ModuleDef::Module(_)) - | hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None, - hir::PathResolution::BuiltinAttr(_) - | hir::PathResolution::ToolModule(_) - | hir::PathResolution::Local(_) - | hir::PathResolution::TypeParam(_) - | hir::PathResolution::ConstParam(_) - | hir::PathResolution::SelfType(_) - | hir::PathResolution::DeriveHelper(_) => return None, - }; - - generic_def - } else if let Some(method_call) = arg_list.syntax().parent().and_then(ast::MethodCallExpr::cast) - { - // recv.method::<$0>() - let method = sema.resolve_method_call(&method_call)?; - method.into() - } else { - return None; - }; - + let (mut generics_def, mut active_parameter, first_arg_is_non_lifetime) = + generic_def_for_node(sema, &arg_list, &token)?; let mut res = SignatureHelp { doc: None, signature: String::new(), @@ -292,9 +261,9 @@ fn signature_help_for_generics( // eg. `None::<u8>` // We'll use the signature of the enum, but include the docs of the variant. res.doc = it.docs(db).map(|it| it.into()); - let it = it.parent_enum(db); - format_to!(res.signature, "enum {}", it.name(db)); - generics_def = it.into(); + let enum_ = it.parent_enum(db); + format_to!(res.signature, "enum {}", enum_.name(db)); + generics_def = enum_.into(); } // These don't have generic args that can be specified hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) => return None, @@ -368,6 +337,83 @@ fn add_assoc_type_bindings( } } +fn signature_help_for_record_lit( + sema: &Semantics<'_, RootDatabase>, + record: ast::RecordExpr, + token: SyntaxToken, +) -> Option<SignatureHelp> { + let active_parameter = record + .record_expr_field_list()? + .syntax() + .children_with_tokens() + .filter_map(syntax::NodeOrToken::into_token) + .filter(|t| t.kind() == syntax::T![,]) + .take_while(|t| t.text_range().start() <= token.text_range().start()) + .count(); + + let mut res = SignatureHelp { + doc: None, + signature: String::new(), + parameters: vec![], + active_parameter: Some(active_parameter), + }; + + let fields; + + let db = sema.db; + let path_res = sema.resolve_path(&record.path()?)?; + if let PathResolution::Def(ModuleDef::Variant(variant)) = path_res { + fields = variant.fields(db); + let en = variant.parent_enum(db); + + res.doc = en.docs(db).map(|it| it.into()); + format_to!(res.signature, "enum {}::{} {{ ", en.name(db), variant.name(db)); + } else { + let adt = match path_res { + PathResolution::SelfType(imp) => imp.self_ty(db).as_adt()?, + PathResolution::Def(ModuleDef::Adt(adt)) => adt, + _ => return None, + }; + + match adt { + hir::Adt::Struct(it) => { + fields = it.fields(db); + res.doc = it.docs(db).map(|it| it.into()); + format_to!(res.signature, "struct {} {{ ", it.name(db)); + } + hir::Adt::Union(it) => { + fields = it.fields(db); + res.doc = it.docs(db).map(|it| it.into()); + format_to!(res.signature, "union {} {{ ", it.name(db)); + } + _ => return None, + } + } + + let mut fields = + fields.into_iter().map(|field| (field.name(db), Some(field))).collect::<FxIndexMap<_, _>>(); + let mut buf = String::new(); + for field in record.record_expr_field_list()?.fields() { + let Some((field, _, ty)) = sema.resolve_record_field(&field) else { continue }; + let name = field.name(db); + format_to!(buf, "{name}: {}", ty.display_truncated(db, Some(20))); + res.push_record_field(&buf); + buf.clear(); + + if let Some(field) = fields.get_mut(&name) { + *field = None; + } + } + for (name, field) in fields { + let Some(field) = field else { continue }; + format_to!(buf, "{name}: {}", field.ty(db).display_truncated(db, Some(20))); + res.push_record_field(&buf); + buf.clear(); + } + res.signature.push_str(" }"); + Some(res) +} + #[cfg(test)] mod tests { use std::iter; @@ -1405,4 +1451,121 @@ fn take<C, Error>( "#]], ); } + + #[test] + fn record_literal() { + check( + r#" +struct Strukt<T, U = ()> { + t: T, + u: U, + unit: (), +} +fn f() { + Strukt { + u: 0, + $0 + } +} +"#, + expect![[r#" + struct Strukt { u: i32, t: T, unit: () } + ------ ^^^^ -------- + "#]], + ); + } + + #[test] + fn record_literal_nonexistent_field() { + check( + r#" +struct Strukt { + a: u8, +} +fn f() { + Strukt { + b: 8, + $0 + } +} +"#, + expect![[r#" + struct Strukt { a: u8 } + ----- + "#]], + ); + } + + #[test] + fn tuple_variant_record_literal() { + check( + r#" +enum Opt { + Some(u8), +} +fn f() { + Opt::Some {$0} +} +"#, + expect![[r#" + enum Opt::Some { 0: u8 } + ^^^^^ + "#]], + ); + check( + r#" +enum Opt { + Some(u8), +} +fn f() { + Opt::Some {0:0,$0} +} +"#, + expect![[r#" + enum Opt::Some { 0: u8 } + ----- + "#]], + ); + } + + #[test] + fn record_literal_self() { + check( + r#" +struct S { t: u8 } +impl S { + fn new() -> Self { + Self { $0 } + } +} + "#, + expect![[r#" + struct S { t: u8 } + ^^^^^ + "#]], + ); + } + + #[test] + fn test_enum_in_nested_method_in_lambda() { + check( + r#" +enum A { + A, + B +} + +fn bar(_: A) { } + +fn main() { + let foo = Foo; + std::thread::spawn(move || { bar(A:$0) } ); +} +"#, + expect![[r#" + fn bar(_: A) + ^^^^ + "#]], + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide/src/static_index.rs b/src/tools/rust-analyzer/crates/ide/src/static_index.rs index a6b30ba13..3f7f6885f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/static_index.rs +++ b/src/tools/rust-analyzer/crates/ide/src/static_index.rs @@ -16,8 +16,7 @@ use crate::{ inlay_hints::AdjustmentHintsMode, moniker::{def_to_moniker, MonikerResult}, parent_module::crates_for, - Analysis, Fold, HoverConfig, HoverDocFormat, HoverResult, InlayHint, InlayHintsConfig, - TryToNav, + Analysis, Fold, HoverConfig, HoverResult, InlayHint, InlayHintsConfig, TryToNav, }; /// A static representation of fully analyzed source code. @@ -107,7 +106,6 @@ impl StaticIndex<'_> { .analysis .inlay_hints( &InlayHintsConfig { - location_links: true, render_colons: true, discriminant_hints: crate::DiscriminantHints::Fieldless, type_hints: true, @@ -138,8 +136,9 @@ impl StaticIndex<'_> { }); let hover_config = HoverConfig { links_in_hover: true, - documentation: Some(HoverDocFormat::Markdown), + documentation: true, keywords: true, + format: crate::HoverDocFormat::Markdown, }; let tokens = tokens.filter(|token| { matches!( diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs index 50371d620..454a250f3 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs @@ -413,11 +413,10 @@ fn traverse( let string = ast::String::cast(token); let string_to_highlight = ast::String::cast(descended_token.clone()); if let Some((string, expanded_string)) = string.zip(string_to_highlight) { - if string.is_raw() { - if inject::ra_fixture(hl, sema, config, &string, &expanded_string).is_some() - { - continue; - } + if string.is_raw() + && inject::ra_fixture(hl, sema, config, &string, &expanded_string).is_some() + { + continue; } highlight_format_string(hl, &string, &expanded_string, range); highlight_escape_string(hl, &string, range.start()); diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs index 2f870d769..fc9b5d3ba 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs @@ -1126,5 +1126,5 @@ fn benchmark_syntax_highlighting_parser() { .filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function)) .count() }; - assert_eq!(hash, 1609); + assert_eq!(hash, 1608); } diff --git a/src/tools/rust-analyzer/crates/ide/src/typing.rs b/src/tools/rust-analyzer/crates/ide/src/typing.rs index eba5a4856..c7e403f6b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/typing.rs +++ b/src/tools/rust-analyzer/crates/ide/src/typing.rs @@ -205,10 +205,8 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { if expr_stmt.semicolon_token().is_some() { return None; } - } else { - if !ast::StmtList::can_cast(binop.syntax().parent()?.kind()) { - return None; - } + } else if !ast::StmtList::can_cast(binop.syntax().parent()?.kind()) { + return None; } let expr = binop.rhs()?; @@ -255,6 +253,10 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') { return None; } + // Good indicator that we will insert into a bad spot, so bail out. + if expr.syntax().descendants().any(|it| it.kind() == SyntaxKind::ERROR) { + return None; + } let offset = let_stmt.syntax().text_range().end(); Some(TextEdit::insert(offset, ";".to_string())) } @@ -409,15 +411,14 @@ mod tests { #[test] fn test_semi_after_let() { - // do_check(r" - // fn foo() { - // let foo =$0 - // } - // ", r" - // fn foo() { - // let foo =; - // } - // "); + type_char_noop( + '=', + r" +fn foo() { + let foo =$0 +} +", + ); type_char( '=', r#" @@ -431,17 +432,25 @@ fn foo() { } "#, ); - // do_check(r" - // fn foo() { - // let foo =$0 - // let bar = 1; - // } - // ", r" - // fn foo() { - // let foo =; - // let bar = 1; - // } - // "); + type_char_noop( + '=', + r#" +fn foo() { + let difference $0(counts: &HashMap<(char, char), u64>, last: char) -> u64 { + // ... + } +} +"#, + ); + type_char_noop( + '=', + r" +fn foo() { + let foo =$0 + let bar = 1; +} +", + ); } #[test] diff --git a/src/tools/rust-analyzer/crates/intern/Cargo.toml b/src/tools/rust-analyzer/crates/intern/Cargo.toml new file mode 100644 index 000000000..c73c368a1 --- /dev/null +++ b/src/tools/rust-analyzer/crates/intern/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "intern" +version = "0.0.0" +description = "TBD" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true + +[lib] +doctest = false + + +[dependencies] +# We need to freeze the version of the crate, as the raw-api feature is considered unstable +dashmap = { version = "=5.4.0", features = ["raw-api"] } +hashbrown = { version = "0.12.1", default-features = false } +once_cell = "1.17.0" +rustc-hash = "1.1.0" diff --git a/src/tools/rust-analyzer/crates/hir-def/src/intern.rs b/src/tools/rust-analyzer/crates/intern/src/lib.rs index f08521a34..fb2903696 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/intern.rs +++ b/src/tools/rust-analyzer/crates/intern/src/lib.rs @@ -14,8 +14,6 @@ use hashbrown::HashMap; use once_cell::sync::OnceCell; use rustc_hash::FxHasher; -use crate::generics::GenericParams; - type InternMap<T> = DashMap<Arc<T>, (), BuildHasherDefault<FxHasher>>; type Guard<T> = dashmap::RwLockWriteGuard< 'static, @@ -204,9 +202,9 @@ pub trait Internable: Hash + Eq + 'static { #[doc(hidden)] macro_rules! _impl_internable { ( $($t:path),+ $(,)? ) => { $( - impl Internable for $t { - fn storage() -> &'static InternStorage<Self> { - static STORAGE: InternStorage<$t> = InternStorage::new(); + impl $crate::Internable for $t { + fn storage() -> &'static $crate::InternStorage<Self> { + static STORAGE: $crate::InternStorage<$t> = $crate::InternStorage::new(); &STORAGE } } @@ -215,13 +213,4 @@ macro_rules! _impl_internable { pub use crate::_impl_internable as impl_internable; -impl_internable!( - crate::type_ref::TypeRef, - crate::type_ref::TraitRef, - crate::type_ref::TypeBound, - crate::path::ModPath, - crate::path::GenericArgs, - crate::attr::AttrInput, - GenericParams, - str, -); +impl_internable!(str,); diff --git a/src/tools/rust-analyzer/crates/limit/Cargo.toml b/src/tools/rust-analyzer/crates/limit/Cargo.toml index 3536f73da..c08886909 100644 --- a/src/tools/rust-analyzer/crates/limit/Cargo.toml +++ b/src/tools/rust-analyzer/crates/limit/Cargo.toml @@ -2,9 +2,11 @@ name = "limit" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [features] tracking = [] diff --git a/src/tools/rust-analyzer/crates/mbe/Cargo.toml b/src/tools/rust-analyzer/crates/mbe/Cargo.toml index bce2fc9a7..82105522e 100644 --- a/src/tools/rust-analyzer/crates/mbe/Cargo.toml +++ b/src/tools/rust-analyzer/crates/mbe/Cargo.toml @@ -2,9 +2,11 @@ name = "mbe" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -12,13 +14,14 @@ doctest = false [dependencies] cov-mark = "2.0.0-pre.1" rustc-hash = "1.1.0" -smallvec = "1.10.0" +smallvec.workspace = true tracing = "0.1.35" -syntax = { path = "../syntax", version = "0.0.0" } -parser = { path = "../parser", version = "0.0.0" } -tt = { path = "../tt", version = "0.0.0" } -stdx = { path = "../stdx", version = "0.0.0" } +# local deps +syntax.workspace = true +parser.workspace = true +tt.workspace = true +stdx.workspace = true [dev-dependencies] -test-utils = { path = "../test-utils" } +test-utils.workspace = true diff --git a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs index 4b7500250..894355fcb 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs @@ -9,7 +9,7 @@ use test_utils::{bench, bench_fixture, skip_slow_tests}; use crate::{ parser::{MetaVarKind, Op, RepeatKind, Separator}, - syntax_node_to_token_tree, DeclarativeMacro, + syntax_node_to_token_tree, tt, DeclarativeMacro, }; #[test] @@ -91,7 +91,14 @@ fn invocation_fixtures(rules: &FxHashMap<String, DeclarativeMacro>) -> Vec<(Stri // So we just skip any error cases and try again let mut try_cnt = 0; loop { - let mut subtree = tt::Subtree::default(); + let mut subtree = tt::Subtree { + delimiter: tt::Delimiter { + open: tt::TokenId::UNSPECIFIED, + close: tt::TokenId::UNSPECIFIED, + kind: tt::DelimiterKind::Invisible, + }, + token_trees: vec![], + }; for op in rule.lhs.iter() { collect_from_op(op, &mut subtree, &mut seed); } @@ -145,7 +152,7 @@ fn invocation_fixtures(rules: &FxHashMap<String, DeclarativeMacro>) -> Vec<(Stri Op::Ident(it) => parent.token_trees.push(tt::Leaf::from(it.clone()).into()), Op::Punct(puncts) => { for punct in puncts { - parent.token_trees.push(tt::Leaf::from(punct.clone()).into()); + parent.token_trees.push(tt::Leaf::from(*punct).into()); } } Op::Repeat { tokens, kind, separator } => { @@ -196,12 +203,15 @@ fn invocation_fixtures(rules: &FxHashMap<String, DeclarativeMacro>) -> Vec<(Stri *seed } fn make_ident(ident: &str) -> tt::TokenTree { - tt::Leaf::Ident(tt::Ident { id: tt::TokenId::unspecified(), text: SmolStr::new(ident) }) - .into() + tt::Leaf::Ident(tt::Ident { + span: tt::TokenId::unspecified(), + text: SmolStr::new(ident), + }) + .into() } fn make_punct(char: char) -> tt::TokenTree { tt::Leaf::Punct(tt::Punct { - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), char, spacing: tt::Spacing::Alone, }) @@ -209,7 +219,7 @@ fn invocation_fixtures(rules: &FxHashMap<String, DeclarativeMacro>) -> Vec<(Stri } fn make_literal(lit: &str) -> tt::TokenTree { tt::Leaf::Literal(tt::Literal { - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), text: SmolStr::new(lit), }) .into() @@ -219,7 +229,11 @@ fn invocation_fixtures(rules: &FxHashMap<String, DeclarativeMacro>) -> Vec<(Stri token_trees: Option<Vec<tt::TokenTree>>, ) -> tt::TokenTree { tt::Subtree { - delimiter: Some(tt::Delimiter { id: tt::TokenId::unspecified(), kind }), + delimiter: tt::Delimiter { + open: tt::TokenId::unspecified(), + close: tt::TokenId::unspecified(), + kind, + }, token_trees: token_trees.unwrap_or_default(), } .into() diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander.rs b/src/tools/rust-analyzer/crates/mbe/src/expander.rs index 100ec6bfb..7537dc322 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander.rs @@ -8,7 +8,7 @@ mod transcriber; use rustc_hash::FxHashMap; use syntax::SmolStr; -use crate::{parser::MetaVarKind, ExpandError, ExpandResult}; +use crate::{parser::MetaVarKind, tt, ExpandError, ExpandResult}; pub(crate) fn expand_rules( rules: &[crate::Rule], @@ -45,7 +45,10 @@ pub(crate) fn expand_rules( transcriber::transcribe(&rule.rhs, &match_.bindings); ExpandResult { value, err: match_.err.or(transcribe_err) } } else { - ExpandResult::only_err(ExpandError::NoMatchingRule) + ExpandResult::with_err( + tt::Subtree { delimiter: tt::Delimiter::unspecified(), token_trees: vec![] }, + ExpandError::NoMatchingRule, + ) } } diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs index 88eae136f..f4ea9e5c8 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs @@ -67,6 +67,7 @@ use syntax::SmolStr; use crate::{ expander::{Binding, Bindings, ExpandResult, Fragment}, parser::{MetaVarKind, Op, RepeatKind, Separator}, + tt, tt_iter::TtIter, ExpandError, MetaTemplate, ValueResult, }; @@ -75,7 +76,8 @@ impl Bindings { fn push_optional(&mut self, name: &SmolStr) { // FIXME: Do we have a better way to represent an empty token ? // Insert an empty subtree for empty token - let tt = tt::Subtree::default().into(); + let tt = + tt::Subtree { delimiter: tt::Delimiter::unspecified(), token_trees: vec![] }.into(); self.inner.insert(name.clone(), Binding::Fragment(Fragment::Tokens(tt))); } @@ -462,9 +464,9 @@ fn match_loop_inner<'t>( } OpDelimited::Op(Op::Subtree { tokens, delimiter }) => { if let Ok(subtree) = src.clone().expect_subtree() { - if subtree.delimiter_kind() == delimiter.map(|it| it.kind) { + if subtree.delimiter.kind == delimiter.kind { item.stack.push(item.dot); - item.dot = tokens.iter_delimited(delimiter.as_ref()); + item.dot = tokens.iter_delimited(Some(delimiter)); cur_items.push(item); } } @@ -663,8 +665,8 @@ fn match_loop(pattern: &MetaTemplate, src: &tt::Subtree) -> Match { } res.add_err(ExpandError::LeftoverTokens); - if let Some(error_reover_item) = error_recover_item { - res.bindings = bindings_builder.build(&error_reover_item); + if let Some(error_recover_item) = error_recover_item { + res.bindings = bindings_builder.build(&error_recover_item); } return res; } @@ -782,7 +784,7 @@ fn match_meta_var(kind: MetaVarKind, input: &mut TtIter<'_>) -> ExpandResult<Opt match neg { None => lit.into(), Some(neg) => tt::TokenTree::Subtree(tt::Subtree { - delimiter: None, + delimiter: tt::Delimiter::unspecified(), token_trees: vec![neg, lit.into()], }), } @@ -810,7 +812,11 @@ fn collect_vars(collector_fun: &mut impl FnMut(SmolStr), pattern: &MetaTemplate) } impl MetaTemplate { fn iter_delimited<'a>(&'a self, delimited: Option<&'a tt::Delimiter>) -> OpDelimitedIter<'a> { - OpDelimitedIter { inner: &self.0, idx: 0, delimited } + OpDelimitedIter { + inner: &self.0, + idx: 0, + delimited: delimited.unwrap_or(&tt::Delimiter::UNSPECIFIED), + } } } @@ -824,20 +830,21 @@ enum OpDelimited<'a> { #[derive(Debug, Clone, Copy)] struct OpDelimitedIter<'a> { inner: &'a [Op], - delimited: Option<&'a tt::Delimiter>, + delimited: &'a tt::Delimiter, idx: usize, } impl<'a> OpDelimitedIter<'a> { fn is_eof(&self) -> bool { - let len = self.inner.len() + if self.delimited.is_some() { 2 } else { 0 }; + let len = self.inner.len() + + if self.delimited.kind != tt::DelimiterKind::Invisible { 2 } else { 0 }; self.idx >= len } fn peek(&self) -> Option<OpDelimited<'a>> { - match self.delimited { - None => self.inner.get(self.idx).map(OpDelimited::Op), - Some(_) => match self.idx { + match self.delimited.kind { + tt::DelimiterKind::Invisible => self.inner.get(self.idx).map(OpDelimited::Op), + _ => match self.idx { 0 => Some(OpDelimited::Open), i if i == self.inner.len() + 1 => Some(OpDelimited::Close), i => self.inner.get(i - 1).map(OpDelimited::Op), @@ -860,7 +867,8 @@ impl<'a> Iterator for OpDelimitedIter<'a> { } fn size_hint(&self) -> (usize, Option<usize>) { - let len = self.inner.len() + if self.delimited.is_some() { 2 } else { 0 }; + let len = self.inner.len() + + if self.delimited.kind != tt::DelimiterKind::Invisible { 2 } else { 0 }; let remain = len.saturating_sub(self.idx); (remain, Some(remain)) } @@ -904,7 +912,10 @@ impl<'a> TtIter<'a> { } else { let puncts = self.expect_glued_punct()?; let token_trees = puncts.into_iter().map(|p| tt::Leaf::Punct(p).into()).collect(); - Ok(tt::TokenTree::Subtree(tt::Subtree { delimiter: None, token_trees })) + Ok(tt::TokenTree::Subtree(tt::Subtree { + delimiter: tt::Delimiter::unspecified(), + token_trees, + })) } } else { self.next().ok_or(()).cloned() @@ -919,7 +930,7 @@ impl<'a> TtIter<'a> { let ident = self.expect_ident_or_underscore()?; Ok(tt::Subtree { - delimiter: None, + delimiter: tt::Delimiter::unspecified(), token_trees: vec![ tt::Leaf::Punct(*punct).into(), tt::Leaf::Ident(ident.clone()).into(), diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs index db0d327bf..dffb40d4b 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs @@ -2,11 +2,11 @@ //! `$ident => foo`, interpolates variables in the template, to get `fn foo() {}` use syntax::SmolStr; -use tt::{Delimiter, Subtree}; use crate::{ expander::{Binding, Bindings, Fragment}, parser::{MetaVarKind, Op, RepeatKind, Separator}, + tt::{self, Delimiter}, ExpandError, ExpandResult, MetaTemplate, }; @@ -44,22 +44,23 @@ impl Bindings { Binding::Missing(it) => Ok(match it { MetaVarKind::Stmt => { Fragment::Tokens(tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), char: ';', spacing: tt::Spacing::Alone, }))) } MetaVarKind::Block => Fragment::Tokens(tt::TokenTree::Subtree(tt::Subtree { - delimiter: Some(tt::Delimiter { - id: tt::TokenId::unspecified(), + delimiter: tt::Delimiter { + open: tt::TokenId::unspecified(), + close: tt::TokenId::unspecified(), kind: tt::DelimiterKind::Brace, - }), + }, token_trees: vec![], })), // FIXME: Meta and Item should get proper defaults MetaVarKind::Meta | MetaVarKind::Item | MetaVarKind::Tt | MetaVarKind::Vis => { Fragment::Tokens(tt::TokenTree::Subtree(tt::Subtree { - delimiter: None, + delimiter: tt::Delimiter::UNSPECIFIED, token_trees: vec![], })) } @@ -71,19 +72,19 @@ impl Bindings { | MetaVarKind::Ident => { Fragment::Tokens(tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text: SmolStr::new_inline("missing"), - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), }))) } MetaVarKind::Lifetime => { Fragment::Tokens(tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text: SmolStr::new_inline("'missing"), - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), }))) } MetaVarKind::Literal => { Fragment::Tokens(tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text: SmolStr::new_inline("\"missing\""), - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), }))) } }), @@ -138,12 +139,12 @@ fn expand_subtree( Op::Ident(it) => arena.push(tt::Leaf::from(it.clone()).into()), Op::Punct(puncts) => { for punct in puncts { - arena.push(tt::Leaf::from(punct.clone()).into()); + arena.push(tt::Leaf::from(*punct).into()); } } Op::Subtree { tokens, delimiter } => { let ExpandResult { value: tt, err: e } = - expand_subtree(ctx, tokens, *delimiter, arena); + expand_subtree(ctx, tokens, Some(*delimiter), arena); err = err.or(e); arena.push(tt.into()); } @@ -170,7 +171,7 @@ fn expand_subtree( arena.push( tt::Leaf::Literal(tt::Literal { text: index.to_string().into(), - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), }) .into(), ); @@ -179,7 +180,13 @@ fn expand_subtree( } // drain the elements added in this instance of expand_subtree let tts = arena.drain(start_elements..).collect(); - ExpandResult { value: tt::Subtree { delimiter, token_trees: tts }, err } + ExpandResult { + value: tt::Subtree { + delimiter: delimiter.unwrap_or_else(tt::Delimiter::unspecified), + token_trees: tts, + }, + err, + } } fn expand_var(ctx: &mut ExpandCtx<'_>, v: &SmolStr, id: tt::TokenId) -> ExpandResult<Fragment> { @@ -201,18 +208,25 @@ fn expand_var(ctx: &mut ExpandCtx<'_>, v: &SmolStr, id: tt::TokenId) -> ExpandRe // ``` // We just treat it a normal tokens let tt = tt::Subtree { - delimiter: None, + delimiter: tt::Delimiter::UNSPECIFIED, token_trees: vec![ - tt::Leaf::from(tt::Punct { char: '$', spacing: tt::Spacing::Alone, id }).into(), - tt::Leaf::from(tt::Ident { text: v.clone(), id }).into(), + tt::Leaf::from(tt::Punct { char: '$', spacing: tt::Spacing::Alone, span: id }) + .into(), + tt::Leaf::from(tt::Ident { text: v.clone(), span: id }).into(), ], } .into(); ExpandResult::ok(Fragment::Tokens(tt)) } else { ctx.bindings.get(v, &mut ctx.nesting).map_or_else( - |e| ExpandResult { value: Fragment::Tokens(tt::TokenTree::empty()), err: Some(e) }, - |it| ExpandResult::ok(it), + |e| ExpandResult { + value: Fragment::Tokens(tt::TokenTree::Subtree(tt::Subtree { + delimiter: tt::Delimiter::unspecified(), + token_trees: vec![], + })), + err: Some(e), + }, + ExpandResult::ok, ) } } @@ -249,7 +263,10 @@ fn expand_repeat( ctx ); return ExpandResult { - value: Fragment::Tokens(Subtree::default().into()), + value: Fragment::Tokens( + tt::Subtree { delimiter: tt::Delimiter::unspecified(), token_trees: vec![] } + .into(), + ), err: Some(ExpandError::LimitExceeded), }; } @@ -258,7 +275,7 @@ fn expand_repeat( continue; } - t.delimiter = None; + t.delimiter = tt::Delimiter::unspecified(); push_subtree(&mut buf, t); if let Some(sep) = separator { @@ -292,7 +309,7 @@ fn expand_repeat( // Check if it is a single token subtree without any delimiter // e.g {Delimiter:None> ['>'] /Delimiter:None>} - let tt = tt::Subtree { delimiter: None, token_trees: buf }.into(); + let tt = tt::Subtree { delimiter: tt::Delimiter::unspecified(), token_trees: buf }.into(); if RepeatKind::OneOrMore == kind && counter == 0 { return ExpandResult { @@ -307,11 +324,12 @@ fn push_fragment(buf: &mut Vec<tt::TokenTree>, fragment: Fragment) { match fragment { Fragment::Tokens(tt::TokenTree::Subtree(tt)) => push_subtree(buf, tt), Fragment::Expr(tt::TokenTree::Subtree(mut tt)) => { - if tt.delimiter.is_none() { - tt.delimiter = Some(tt::Delimiter { - id: tt::TokenId::unspecified(), + if tt.delimiter.kind == tt::DelimiterKind::Invisible { + tt.delimiter = tt::Delimiter { + open: tt::TokenId::UNSPECIFIED, + close: tt::TokenId::UNSPECIFIED, kind: tt::DelimiterKind::Parenthesis, - }) + }; } buf.push(tt.into()) } @@ -320,8 +338,8 @@ fn push_fragment(buf: &mut Vec<tt::TokenTree>, fragment: Fragment) { } fn push_subtree(buf: &mut Vec<tt::TokenTree>, tt: tt::Subtree) { - match tt.delimiter { - None => buf.extend(tt.token_trees), - Some(_) => buf.push(tt.into()), + match tt.delimiter.kind { + tt::DelimiterKind::Invisible => buf.extend(tt.token_trees), + _ => buf.push(tt.into()), } } diff --git a/src/tools/rust-analyzer/crates/mbe/src/lib.rs b/src/tools/rust-analyzer/crates/mbe/src/lib.rs index 2373db97a..ac107a0d6 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/lib.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/lib.rs @@ -18,6 +18,8 @@ mod to_parser_input; mod benchmark; mod token_map; +use ::tt::token_id as tt; + use std::fmt; use crate::{ @@ -26,8 +28,8 @@ use crate::{ }; // FIXME: we probably should re-think `token_tree_to_syntax_node` interfaces +pub use self::tt::{Delimiter, DelimiterKind, Punct}; pub use ::parser::TopEntryPoint; -pub use tt::{Delimiter, DelimiterKind, Punct}; pub use crate::{ syntax_bridge::{ @@ -125,24 +127,26 @@ impl Shift { // Find the max token id inside a subtree fn max_id(subtree: &tt::Subtree) -> Option<u32> { - let filter = |tt: &_| match tt { - tt::TokenTree::Subtree(subtree) => { - let tree_id = max_id(subtree); - match subtree.delimiter { - Some(it) if it.id != tt::TokenId::unspecified() => { - Some(tree_id.map_or(it.id.0, |t| t.max(it.id.0))) + let filter = + |tt: &_| match tt { + tt::TokenTree::Subtree(subtree) => { + let tree_id = max_id(subtree); + if subtree.delimiter.open != tt::TokenId::unspecified() { + Some(tree_id.map_or(subtree.delimiter.open.0, |t| { + t.max(subtree.delimiter.open.0) + })) + } else { + tree_id } - _ => tree_id, } - } - tt::TokenTree::Leaf(leaf) => { - let &(tt::Leaf::Ident(tt::Ident { id, .. }) - | tt::Leaf::Punct(tt::Punct { id, .. }) - | tt::Leaf::Literal(tt::Literal { id, .. })) = leaf; + tt::TokenTree::Leaf(leaf) => { + let &(tt::Leaf::Ident(tt::Ident { span, .. }) + | tt::Leaf::Punct(tt::Punct { span, .. }) + | tt::Leaf::Literal(tt::Literal { span, .. })) = leaf; - (id != tt::TokenId::unspecified()).then_some(id.0) - } - }; + (span != tt::TokenId::unspecified()).then_some(span.0) + } + }; subtree.token_trees.iter().filter_map(filter).max() } } @@ -152,14 +156,13 @@ impl Shift { for t in &mut tt.token_trees { match t { tt::TokenTree::Leaf( - tt::Leaf::Ident(tt::Ident { id, .. }) - | tt::Leaf::Punct(tt::Punct { id, .. }) - | tt::Leaf::Literal(tt::Literal { id, .. }), - ) => *id = self.shift(*id), + tt::Leaf::Ident(tt::Ident { span, .. }) + | tt::Leaf::Punct(tt::Punct { span, .. }) + | tt::Leaf::Literal(tt::Literal { span, .. }), + ) => *span = self.shift(*span), tt::TokenTree::Subtree(tt) => { - if let Some(it) = tt.delimiter.as_mut() { - it.id = self.shift(it.id); - } + tt.delimiter.open = self.shift(tt.delimiter.open); + tt.delimiter.close = self.shift(tt.delimiter.close); self.shift_all(tt) } } @@ -216,7 +219,7 @@ impl DeclarativeMacro { let mut src = TtIter::new(tt); let mut rules = Vec::new(); - if Some(tt::DelimiterKind::Brace) == tt.delimiter_kind() { + if tt::DelimiterKind::Brace == tt.delimiter.kind { cov_mark::hit!(parse_macro_def_rules); while src.len() > 0 { let rule = Rule::parse(&mut src, true)?; @@ -325,6 +328,10 @@ impl<T, E> ValueResult<T, E> { Self { value, err: None } } + pub fn with_err(value: T, err: E) -> Self { + Self { value, err: Some(err) } + } + pub fn only_err(err: E) -> Self where T: Default, diff --git a/src/tools/rust-analyzer/crates/mbe/src/parser.rs b/src/tools/rust-analyzer/crates/mbe/src/parser.rs index fad905e97..fd3d64719 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/parser.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/parser.rs @@ -4,7 +4,7 @@ use smallvec::{smallvec, SmallVec}; use syntax::SmolStr; -use crate::{tt_iter::TtIter, ParseError}; +use crate::{tt, tt_iter::TtIter, ParseError}; /// Consider /// @@ -54,7 +54,7 @@ pub(crate) enum Op { Ignore { name: SmolStr, id: tt::TokenId }, Index { depth: u32 }, Repeat { tokens: MetaTemplate, kind: RepeatKind, separator: Option<Separator> }, - Subtree { tokens: MetaTemplate, delimiter: Option<tt::Delimiter> }, + Subtree { tokens: MetaTemplate, delimiter: tt::Delimiter }, Literal(tt::Literal), Punct(SmallVec<[tt::Punct; 3]>), Ident(tt::Ident), @@ -126,17 +126,17 @@ fn next_op( src.next().expect("first token already peeked"); // Note that the '$' itself is a valid token inside macro_rules. let second = match src.next() { - None => return Ok(Op::Punct(smallvec![p.clone()])), + None => return Ok(Op::Punct(smallvec![*p])), Some(it) => it, }; match second { - tt::TokenTree::Subtree(subtree) => match subtree.delimiter_kind() { - Some(tt::DelimiterKind::Parenthesis) => { + tt::TokenTree::Subtree(subtree) => match subtree.delimiter.kind { + tt::DelimiterKind::Parenthesis => { let (separator, kind) = parse_repeat(src)?; let tokens = MetaTemplate::parse(subtree, mode)?; Op::Repeat { tokens, separator, kind } } - Some(tt::DelimiterKind::Brace) => match mode { + tt::DelimiterKind::Brace => match mode { Mode::Template => { parse_metavar_expr(&mut TtIter::new(subtree)).map_err(|()| { ParseError::unexpected("invalid metavariable expression") @@ -157,18 +157,18 @@ fn next_op( tt::TokenTree::Leaf(leaf) => match leaf { tt::Leaf::Ident(ident) if ident.text == "crate" => { // We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path. - Op::Ident(tt::Ident { text: "$crate".into(), id: ident.id }) + Op::Ident(tt::Ident { text: "$crate".into(), span: ident.span }) } tt::Leaf::Ident(ident) => { let kind = eat_fragment_kind(src, mode)?; let name = ident.text.clone(); - let id = ident.id; + let id = ident.span; Op::Var { name, kind, id } } tt::Leaf::Literal(lit) if is_boolean_literal(lit) => { let kind = eat_fragment_kind(src, mode)?; let name = lit.text.clone(); - let id = lit.id; + let id = lit.span; Op::Var { name, kind, id } } tt::Leaf::Punct(punct @ tt::Punct { char: '$', .. }) => match mode { @@ -284,7 +284,7 @@ fn parse_metavar_expr(src: &mut TtIter<'_>) -> Result<Op, ()> { let func = src.expect_ident()?; let args = src.expect_subtree()?; - if args.delimiter_kind() != Some(tt::DelimiterKind::Parenthesis) { + if args.delimiter.kind != tt::DelimiterKind::Parenthesis { return Err(()); } @@ -293,7 +293,7 @@ fn parse_metavar_expr(src: &mut TtIter<'_>) -> Result<Op, ()> { let op = match &*func.text { "ignore" => { let ident = args.expect_ident()?; - Op::Ignore { name: ident.text.clone(), id: ident.id } + Op::Ignore { name: ident.text.clone(), id: ident.span } } "index" => { let depth = if args.len() == 0 { 0 } else { args.expect_u32_literal()? }; diff --git a/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge.rs b/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge.rs index 5c9650556..fb5313401 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge.rs @@ -8,9 +8,16 @@ use syntax::{ SyntaxKind::*, SyntaxNode, SyntaxToken, SyntaxTreeBuilder, TextRange, TextSize, WalkEvent, T, }; -use tt::buffer::{Cursor, TokenBuffer}; -use crate::{to_parser_input::to_parser_input, tt_iter::TtIter, TokenMap}; +use crate::{ + to_parser_input::to_parser_input, + tt::{ + self, + buffer::{Cursor, TokenBuffer}, + }, + tt_iter::TtIter, + TokenMap, +}; #[cfg(test)] mod tests; @@ -74,9 +81,10 @@ pub fn token_tree_to_syntax_node( entry_point: parser::TopEntryPoint, ) -> (Parse<SyntaxNode>, TokenMap) { let buffer = match tt { - tt::Subtree { delimiter: None, token_trees } => { - TokenBuffer::from_tokens(token_trees.as_slice()) - } + tt::Subtree { + delimiter: tt::Delimiter { kind: tt::DelimiterKind::Invisible, .. }, + token_trees, + } => TokenBuffer::from_tokens(token_trees.as_slice()), _ => TokenBuffer::from_subtree(tt), }; let parser_input = to_parser_input(&buffer); @@ -87,13 +95,15 @@ pub fn token_tree_to_syntax_node( parser::Step::Token { kind, n_input_tokens: n_raw_tokens } => { tree_sink.token(kind, n_raw_tokens) } + parser::Step::FloatSplit { ends_in_dot: has_pseudo_dot } => { + tree_sink.float_split(has_pseudo_dot) + } parser::Step::Enter { kind } => tree_sink.start_node(kind), parser::Step::Exit => tree_sink.finish_node(), parser::Step::Error { msg } => tree_sink.error(msg.to_string()), } } - let (parse, range_map) = tree_sink.finish(); - (parse, range_map) + tree_sink.finish() } /// Convert a string to a `TokenTree` @@ -132,7 +142,7 @@ pub fn parse_exprs_with_sep(tt: &tt::Subtree, sep: char) -> Vec<tt::Subtree> { res.push(match expanded.value { None => break, Some(tt @ tt::TokenTree::Leaf(_)) => { - tt::Subtree { delimiter: None, token_trees: vec![tt] } + tt::Subtree { delimiter: tt::Delimiter::unspecified(), token_trees: vec![tt] } } Some(tt::TokenTree::Subtree(tt)) => tt, }); @@ -145,7 +155,10 @@ pub fn parse_exprs_with_sep(tt: &tt::Subtree, sep: char) -> Vec<tt::Subtree> { } if iter.peek_n(0).is_some() { - res.push(tt::Subtree { delimiter: None, token_trees: iter.cloned().collect() }); + res.push(tt::Subtree { + delimiter: tt::Delimiter::unspecified(), + token_trees: iter.cloned().collect(), + }); } res @@ -159,7 +172,7 @@ fn convert_tokens<C: TokenConverter>(conv: &mut C) -> tt::Subtree { } let entry = StackEntry { - subtree: tt::Subtree { delimiter: None, ..Default::default() }, + subtree: tt::Subtree { delimiter: tt::Delimiter::unspecified(), token_trees: vec![] }, // never used (delimiter is `None`) idx: !0, open_range: TextRange::empty(TextSize::of('.')), @@ -186,7 +199,7 @@ fn convert_tokens<C: TokenConverter>(conv: &mut C) -> tt::Subtree { if let Some(tt::TokenTree::Leaf(tt::Leaf::Literal(lit))) = sub.token_trees.get_mut(2) { - lit.id = id + lit.span = id } } tt @@ -199,13 +212,14 @@ fn convert_tokens<C: TokenConverter>(conv: &mut C) -> tt::Subtree { assert_eq!(range.len(), TextSize::of('.')); } - if let Some(delim) = subtree.delimiter { - let expected = match delim.kind { - tt::DelimiterKind::Parenthesis => T![')'], - tt::DelimiterKind::Brace => T!['}'], - tt::DelimiterKind::Bracket => T![']'], - }; + let expected = match subtree.delimiter.kind { + tt::DelimiterKind::Parenthesis => Some(T![')']), + tt::DelimiterKind::Brace => Some(T!['}']), + tt::DelimiterKind::Bracket => Some(T![']']), + tt::DelimiterKind::Invisible => None, + }; + if let Some(expected) = expected { if kind == expected { if let Some(entry) = stack.pop() { conv.id_alloc().close_delim(entry.idx, Some(range)); @@ -223,9 +237,11 @@ fn convert_tokens<C: TokenConverter>(conv: &mut C) -> tt::Subtree { }; if let Some(kind) = delim { - let mut subtree = tt::Subtree::default(); let (id, idx) = conv.id_alloc().open_delim(range, synth_id); - subtree.delimiter = Some(tt::Delimiter { id, kind }); + let subtree = tt::Subtree { + delimiter: tt::Delimiter { open: id, close: tt::TokenId::UNSPECIFIED, kind }, + token_trees: vec![], + }; stack.push(StackEntry { subtree, idx, open_range: range }); continue; } @@ -240,13 +256,20 @@ fn convert_tokens<C: TokenConverter>(conv: &mut C) -> tt::Subtree { panic!("Token from lexer must be single char: token = {token:#?}"); } }; - tt::Leaf::from(tt::Punct { char, spacing, id: conv.id_alloc().alloc(range, synth_id) }) - .into() + tt::Leaf::from(tt::Punct { + char, + spacing, + span: conv.id_alloc().alloc(range, synth_id), + }) + .into() } else { macro_rules! make_leaf { ($i:ident) => { - tt::$i { id: conv.id_alloc().alloc(range, synth_id), text: token.to_text(conv) } - .into() + tt::$i { + span: conv.id_alloc().alloc(range, synth_id), + text: token.to_text(conv), + } + .into() }; } let leaf: tt::Leaf = match kind { @@ -261,14 +284,14 @@ fn convert_tokens<C: TokenConverter>(conv: &mut C) -> tt::Subtree { let apostrophe = tt::Leaf::from(tt::Punct { char: '\'', spacing: tt::Spacing::Joint, - id: conv.id_alloc().alloc(r, synth_id), + span: conv.id_alloc().alloc(r, synth_id), }); result.push(apostrophe.into()); let r = TextRange::at(range.start() + char_unit, range.len() - char_unit); let ident = tt::Leaf::from(tt::Ident { text: SmolStr::new(&token.to_text(conv)[1..]), - id: conv.id_alloc().alloc(r, synth_id), + span: conv.id_alloc().alloc(r, synth_id), }); result.push(ident.into()); continue; @@ -289,11 +312,12 @@ fn convert_tokens<C: TokenConverter>(conv: &mut C) -> tt::Subtree { conv.id_alloc().close_delim(entry.idx, None); let leaf: tt::Leaf = tt::Punct { - id: conv.id_alloc().alloc(entry.open_range, None), - char: match entry.subtree.delimiter.unwrap().kind { + span: conv.id_alloc().alloc(entry.open_range, None), + char: match entry.subtree.delimiter.kind { tt::DelimiterKind::Parenthesis => '(', tt::DelimiterKind::Brace => '{', tt::DelimiterKind::Bracket => '[', + tt::DelimiterKind::Invisible => '$', }, spacing: tt::Spacing::Alone, } @@ -373,10 +397,11 @@ fn convert_doc_comment(token: &syntax::SyntaxToken) -> Option<Vec<tt::TokenTree> token_trees.push(mk_punct('!')); } token_trees.push(tt::TokenTree::from(tt::Subtree { - delimiter: Some(tt::Delimiter { + delimiter: tt::Delimiter { + open: tt::TokenId::UNSPECIFIED, + close: tt::TokenId::UNSPECIFIED, kind: tt::DelimiterKind::Bracket, - id: tt::TokenId::unspecified(), - }), + }, token_trees: meta_tkns, })); @@ -386,7 +411,7 @@ fn convert_doc_comment(token: &syntax::SyntaxToken) -> Option<Vec<tt::TokenTree> fn mk_ident(s: &str) -> tt::TokenTree { tt::TokenTree::from(tt::Leaf::from(tt::Ident { text: s.into(), - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), })) } @@ -394,12 +419,12 @@ fn convert_doc_comment(token: &syntax::SyntaxToken) -> Option<Vec<tt::TokenTree> tt::TokenTree::from(tt::Leaf::from(tt::Punct { char: c, spacing: tt::Spacing::Alone, - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), })) } fn mk_doc_literal(comment: &ast::Comment) -> tt::TokenTree { - let lit = tt::Literal { text: doc_comment_text(comment), id: tt::TokenId::unspecified() }; + let lit = tt::Literal { text: doc_comment_text(comment), span: tt::TokenId::unspecified() }; tt::TokenTree::from(tt::Leaf::from(lit)) } @@ -761,18 +786,56 @@ impl<'a> TtTreeSink<'a> { } } -fn delim_to_str(d: tt::DelimiterKind, closing: bool) -> &'static str { +fn delim_to_str(d: tt::DelimiterKind, closing: bool) -> Option<&'static str> { let texts = match d { tt::DelimiterKind::Parenthesis => "()", tt::DelimiterKind::Brace => "{}", tt::DelimiterKind::Bracket => "[]", + tt::DelimiterKind::Invisible => return None, }; let idx = closing as usize; - &texts[idx..texts.len() - (1 - idx)] + Some(&texts[idx..texts.len() - (1 - idx)]) } impl<'a> TtTreeSink<'a> { + /// Parses a float literal as if it was a one to two name ref nodes with a dot inbetween. + /// This occurs when a float literal is used as a field access. + fn float_split(&mut self, has_pseudo_dot: bool) { + let (text, _span) = match self.cursor.token_tree() { + Some(tt::buffer::TokenTreeRef::Leaf(tt::Leaf::Literal(lit), _)) => { + (lit.text.as_str(), lit.span) + } + _ => unreachable!(), + }; + match text.split_once('.') { + Some((left, right)) => { + assert!(!left.is_empty()); + self.inner.start_node(SyntaxKind::NAME_REF); + self.inner.token(SyntaxKind::INT_NUMBER, left); + self.inner.finish_node(); + + // here we move the exit up, the original exit has been deleted in process + self.inner.finish_node(); + + self.inner.token(SyntaxKind::DOT, "."); + + if has_pseudo_dot { + assert!(right.is_empty(), "{left}.{right}"); + } else { + self.inner.start_node(SyntaxKind::NAME_REF); + self.inner.token(SyntaxKind::INT_NUMBER, right); + self.inner.finish_node(); + + // the parser creates an unbalanced start node, we are required to close it here + self.inner.finish_node(); + } + } + None => unreachable!(), + } + self.cursor = self.cursor.bump(); + } + fn token(&mut self, kind: SyntaxKind, mut n_tokens: u8) { if kind == LIFETIME_IDENT { n_tokens = 2; @@ -790,13 +853,16 @@ impl<'a> TtTreeSink<'a> { Some(tt::buffer::TokenTreeRef::Leaf(leaf, _)) => { // Mark the range if needed let (text, id) = match leaf { - tt::Leaf::Ident(ident) => (ident.text.as_str(), ident.id), + tt::Leaf::Ident(ident) => (ident.text.as_str(), ident.span), tt::Leaf::Punct(punct) => { assert!(punct.char.is_ascii()); tmp = punct.char as u8; - (std::str::from_utf8(std::slice::from_ref(&tmp)).unwrap(), punct.id) + ( + std::str::from_utf8(std::slice::from_ref(&tmp)).unwrap(), + punct.span, + ) } - tt::Leaf::Literal(lit) => (lit.text.as_str(), lit.id), + tt::Leaf::Literal(lit) => (lit.text.as_str(), lit.span), }; let range = TextRange::at(self.text_pos, TextSize::of(text)); self.token_map.insert(id, range); @@ -805,10 +871,10 @@ impl<'a> TtTreeSink<'a> { } Some(tt::buffer::TokenTreeRef::Subtree(subtree, _)) => { self.cursor = self.cursor.subtree().unwrap(); - match subtree.delimiter { - Some(d) => { - self.open_delims.insert(d.id, self.text_pos); - delim_to_str(d.kind, false) + match delim_to_str(subtree.delimiter.kind, false) { + Some(it) => { + self.open_delims.insert(subtree.delimiter.open, self.text_pos); + it } None => continue, } @@ -816,15 +882,21 @@ impl<'a> TtTreeSink<'a> { None => { let parent = self.cursor.end().unwrap(); self.cursor = self.cursor.bump(); - match parent.delimiter { - Some(d) => { - if let Some(open_delim) = self.open_delims.get(&d.id) { + match delim_to_str(parent.delimiter.kind, true) { + Some(it) => { + if let Some(open_delim) = + self.open_delims.get(&parent.delimiter.open) + { let open_range = TextRange::at(*open_delim, TextSize::of('(')); let close_range = TextRange::at(self.text_pos, TextSize::of('(')); - self.token_map.insert_delim(d.id, open_range, close_range); + self.token_map.insert_delim( + parent.delimiter.open, + open_range, + close_range, + ); } - delim_to_str(d.kind, true) + it } None => continue, } diff --git a/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge/tests.rs b/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge/tests.rs index c1a608365..fa0125f3e 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge/tests.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/syntax_bridge/tests.rs @@ -29,8 +29,8 @@ fn check_punct_spacing(fixture: &str) { let mut cursor = buf.begin(); while !cursor.eof() { while let Some(token_tree) = cursor.token_tree() { - if let TokenTreeRef::Leaf(Leaf::Punct(Punct { spacing, id, .. }), _) = token_tree { - if let Some(expected) = annotations.remove(id) { + if let TokenTreeRef::Leaf(Leaf::Punct(Punct { spacing, span, .. }), _) = token_tree { + if let Some(expected) = annotations.remove(span) { assert_eq!(expected, *spacing); } } diff --git a/src/tools/rust-analyzer/crates/mbe/src/to_parser_input.rs b/src/tools/rust-analyzer/crates/mbe/src/to_parser_input.rs index 7013aa58b..051e20b3a 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/to_parser_input.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/to_parser_input.rs @@ -2,7 +2,8 @@ //! format that works for our parser. use syntax::{SyntaxKind, SyntaxKind::*, T}; -use tt::buffer::TokenBuffer; + +use crate::tt::buffer::TokenBuffer; pub(crate) fn to_parser_input(buffer: &TokenBuffer<'_>) -> parser::Input { let mut res = parser::Input::default(); @@ -44,6 +45,13 @@ pub(crate) fn to_parser_input(buffer: &TokenBuffer<'_>) -> parser::Input { .unwrap_or_else(|| panic!("Fail to convert given literal {:#?}", &lit)); res.push(kind); + + if kind == FLOAT_NUMBER && !inner_text.ends_with('.') { + // Tag the token as joint if it is float with a fractional part + // we use this jointness to inform the parser about what token split + // event to emit when we encounter a float literal in a field access + res.was_joint(); + } } tt::Leaf::Ident(ident) => match ident.text.as_ref() { "_" => res.push(T![_]), @@ -70,23 +78,25 @@ pub(crate) fn to_parser_input(buffer: &TokenBuffer<'_>) -> parser::Input { cursor.bump() } Some(tt::buffer::TokenTreeRef::Subtree(subtree, _)) => { - if let Some(d) = subtree.delimiter_kind() { - res.push(match d { - tt::DelimiterKind::Parenthesis => T!['('], - tt::DelimiterKind::Brace => T!['{'], - tt::DelimiterKind::Bracket => T!['['], - }); + if let Some(kind) = match subtree.delimiter.kind { + tt::DelimiterKind::Parenthesis => Some(T!['(']), + tt::DelimiterKind::Brace => Some(T!['{']), + tt::DelimiterKind::Bracket => Some(T!['[']), + tt::DelimiterKind::Invisible => None, + } { + res.push(kind); } cursor.subtree().unwrap() } None => match cursor.end() { Some(subtree) => { - if let Some(d) = subtree.delimiter_kind() { - res.push(match d { - tt::DelimiterKind::Parenthesis => T![')'], - tt::DelimiterKind::Brace => T!['}'], - tt::DelimiterKind::Bracket => T![']'], - }) + if let Some(kind) = match subtree.delimiter.kind { + tt::DelimiterKind::Parenthesis => Some(T![')']), + tt::DelimiterKind::Brace => Some(T!['}']), + tt::DelimiterKind::Bracket => Some(T![']']), + tt::DelimiterKind::Invisible => None, + } { + res.push(kind); } cursor.bump() } diff --git a/src/tools/rust-analyzer/crates/mbe/src/tt_iter.rs b/src/tools/rust-analyzer/crates/mbe/src/tt_iter.rs index bee7b5de6..f744481f3 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/tt_iter.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/tt_iter.rs @@ -3,9 +3,8 @@ use smallvec::{smallvec, SmallVec}; use syntax::SyntaxKind; -use tt::buffer::TokenBuffer; -use crate::{to_parser_input::to_parser_input, ExpandError, ExpandResult}; +use crate::{to_parser_input::to_parser_input, tt, ExpandError, ExpandResult}; #[derive(Debug, Clone)] pub(crate) struct TtIter<'a> { @@ -114,7 +113,7 @@ impl<'a> TtIter<'a> { ('.', '.', Some('.' | '=')) | ('<', '<', Some('=')) | ('>', '>', Some('=')) => { let _ = self.next().unwrap(); let _ = self.next().unwrap(); - Ok(smallvec![first, second.clone(), third.unwrap().clone()]) + Ok(smallvec![first, *second, *third.unwrap()]) } ('-' | '!' | '*' | '/' | '&' | '%' | '^' | '+' | '<' | '=' | '>' | '|', '=', _) | ('-' | '=' | '>', '>', _) @@ -125,7 +124,7 @@ impl<'a> TtIter<'a> { | ('<', '<', _) | ('|', '|', _) => { let _ = self.next().unwrap(); - Ok(smallvec![first, second.clone()]) + Ok(smallvec![first, *second]) } _ => Ok(smallvec![first]), } @@ -135,7 +134,7 @@ impl<'a> TtIter<'a> { &mut self, entry_point: parser::PrefixEntryPoint, ) -> ExpandResult<Option<tt::TokenTree>> { - let buffer = TokenBuffer::from_tokens(self.inner.as_slice()); + let buffer = tt::buffer::TokenBuffer::from_tokens(self.inner.as_slice()); let parser_input = to_parser_input(&buffer); let tree_traversal = entry_point.parse(&parser_input); @@ -151,6 +150,11 @@ impl<'a> TtIter<'a> { cursor = cursor.bump_subtree(); } } + parser::Step::FloatSplit { .. } => { + // FIXME: We need to split the tree properly here, but mutating the token trees + // in the buffer is somewhat tricky to pull off. + cursor = cursor.bump_subtree(); + } parser::Step::Enter { .. } | parser::Step::Exit => (), parser::Step::Error { .. } => error = true, } @@ -167,19 +171,18 @@ impl<'a> TtIter<'a> { if cursor.is_root() { while curr != cursor { - if let Some(token) = curr.token_tree() { - res.push(token); - } + let Some(token) = curr.token_tree() else { break }; + res.push(token.cloned()); curr = curr.bump(); } } + self.inner = self.inner.as_slice()[res.len()..].iter(); let res = match res.len() { - 1 => Some(res[0].cloned()), - 0 => None, + 0 | 1 => res.pop(), _ => Some(tt::TokenTree::Subtree(tt::Subtree { - delimiter: None, - token_trees: res.into_iter().map(|it| it.cloned()).collect(), + delimiter: tt::Delimiter::unspecified(), + token_trees: res, })), }; ExpandResult { value: res, err } diff --git a/src/tools/rust-analyzer/crates/parser/Cargo.toml b/src/tools/rust-analyzer/crates/parser/Cargo.toml index d1420de89..6e962abd7 100644 --- a/src/tools/rust-analyzer/crates/parser/Cargo.toml +++ b/src/tools/rust-analyzer/crates/parser/Cargo.toml @@ -2,18 +2,23 @@ name = "parser" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false [dependencies] drop_bomb = "0.1.5" -rustc_lexer = { version = "725.0.0", package = "rustc-ap-rustc_lexer" } -limit = { path = "../limit", version = "0.0.0" } +rustc_lexer = { version = "727.0.0", package = "rustc-ap-rustc_lexer" } + +limit.workspace = true [dev-dependencies] expect-test = "1.4.0" -sourcegen = { path = "../sourcegen" } + +stdx.workspace = true +sourcegen.workspace = true diff --git a/src/tools/rust-analyzer/crates/parser/src/event.rs b/src/tools/rust-analyzer/crates/parser/src/event.rs index b0e70e794..577eb0967 100644 --- a/src/tools/rust-analyzer/crates/parser/src/event.rs +++ b/src/tools/rust-analyzer/crates/parser/src/event.rs @@ -74,7 +74,13 @@ pub(crate) enum Event { kind: SyntaxKind, n_raw_tokens: u8, }, - + /// When we parse `foo.0.0` or `foo. 0. 0` the lexer will hand us a float literal + /// instead of an integer literal followed by a dot as the lexer has no contextual knowledge. + /// This event instructs whatever consumes the events to split the float literal into + /// the corresponding parts. + FloatSplitHack { + ends_in_dot: bool, + }, Error { msg: String, }, @@ -125,6 +131,11 @@ pub(super) fn process(mut events: Vec<Event>) -> Output { Event::Token { kind, n_raw_tokens } => { res.token(kind, n_raw_tokens); } + Event::FloatSplitHack { ends_in_dot } => { + res.float_split_hack(ends_in_dot); + let ev = mem::replace(&mut events[i + 1], Event::tombstone()); + assert!(matches!(ev, Event::Finish), "{ev:?}"); + } Event::Error { msg } => res.error(msg), } } diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar.rs b/src/tools/rust-analyzer/crates/parser/src/grammar.rs index 485b612f0..15ec9e167 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar.rs @@ -200,6 +200,8 @@ impl BlockLike { } } +const VISIBILITY_FIRST: TokenSet = TokenSet::new(&[T![pub], T![crate]]); + fn opt_visibility(p: &mut Parser<'_>, in_tuple_field: bool) -> bool { match p.current() { T![pub] => { @@ -340,3 +342,31 @@ fn error_block(p: &mut Parser<'_>, message: &str) { p.eat(T!['}']); m.complete(p, ERROR); } + +/// The `parser` passed this is required to at least consume one token if it returns `true`. +/// If the `parser` returns false, parsing will stop. +fn delimited( + p: &mut Parser<'_>, + bra: SyntaxKind, + ket: SyntaxKind, + delim: SyntaxKind, + first_set: TokenSet, + mut parser: impl FnMut(&mut Parser<'_>) -> bool, +) { + p.bump(bra); + while !p.at(ket) && !p.at(EOF) { + if !parser(p) { + break; + } + if !p.at(delim) { + if p.at_ts(first_set) { + p.error(format!("expected {:?}", delim)); + } else { + break; + } + } else { + p.bump(delim); + } + } + p.expect(ket); +} diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs index 0cf6a16f8..4ecaa6e6a 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs @@ -1,5 +1,7 @@ use super::*; +pub(super) const ATTRIBUTE_FIRST: TokenSet = TokenSet::new(&[T![#]]); + pub(super) fn inner_attrs(p: &mut Parser<'_>) { while p.at(T![#]) && p.nth(1) == T![!] { attr(p, true); diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs index 8932330b8..4b080102a 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs @@ -1,5 +1,7 @@ mod atom; +use crate::grammar::attributes::ATTRIBUTE_FIRST; + use super::*; pub(crate) use self::atom::{block_expr, match_arm_list}; @@ -68,6 +70,12 @@ pub(super) fn stmt(p: &mut Parser<'_>, semicolon: Semicolon) { Err(m) => m, }; + if !p.at_ts(EXPR_FIRST) { + p.err_and_bump("expected expression, item or let statement"); + m.abandon(p); + return; + } + if let Some((cm, blocklike)) = expr_stmt(p, Some(m)) { if !(p.at(T!['}']) || (semicolon != Semicolon::Required && p.at(EOF))) { // test no_semi_after_block @@ -227,6 +235,12 @@ fn expr_bp( attributes::outer_attrs(p); m }); + + if !p.at_ts(EXPR_FIRST) { + p.err_recover("expected expression", atom::EXPR_RECOVERY_SET); + m.abandon(p); + return None; + } let mut lhs = match lhs(p, r) { Some((lhs, blocklike)) => { let lhs = lhs.extend_to(p, m); @@ -379,7 +393,7 @@ fn postfix_expr( // } T!['('] if allow_calls => call_expr(p, lhs), T!['['] if allow_calls => index_expr(p, lhs), - T![.] => match postfix_dot_expr(p, lhs) { + T![.] => match postfix_dot_expr::<false>(p, lhs) { Ok(it) => it, Err(it) => { lhs = it; @@ -393,35 +407,44 @@ fn postfix_expr( block_like = BlockLike::NotBlock; } return (lhs, block_like); +} - fn postfix_dot_expr( - p: &mut Parser<'_>, - lhs: CompletedMarker, - ) -> Result<CompletedMarker, CompletedMarker> { +fn postfix_dot_expr<const FLOAT_RECOVERY: bool>( + p: &mut Parser<'_>, + lhs: CompletedMarker, +) -> Result<CompletedMarker, CompletedMarker> { + if !FLOAT_RECOVERY { assert!(p.at(T![.])); - if p.nth(1) == IDENT && (p.nth(2) == T!['('] || p.nth_at(2, T![::])) { - return Ok(method_call_expr(p, lhs)); - } + } + let nth1 = if FLOAT_RECOVERY { 0 } else { 1 }; + let nth2 = if FLOAT_RECOVERY { 1 } else { 2 }; - // test await_expr - // fn foo() { - // x.await; - // x.0.await; - // x.0().await?.hello(); - // } - if p.nth(1) == T![await] { - let m = lhs.precede(p); - p.bump(T![.]); - p.bump(T![await]); - return Ok(m.complete(p, AWAIT_EXPR)); - } + if p.nth(nth1) == IDENT && (p.nth(nth2) == T!['('] || p.nth_at(nth2, T![::])) { + return Ok(method_call_expr::<FLOAT_RECOVERY>(p, lhs)); + } - if p.at(T![..=]) || p.at(T![..]) { - return Err(lhs); + // test await_expr + // fn foo() { + // x.await; + // x.0.await; + // x.0().await?.hello(); + // x.0.0.await; + // x.0. await; + // } + if p.nth(nth1) == T![await] { + let m = lhs.precede(p); + if !FLOAT_RECOVERY { + p.bump(T![.]); } + p.bump(T![await]); + return Ok(m.complete(p, AWAIT_EXPR)); + } - Ok(field_expr(p, lhs)) + if p.at(T![..=]) || p.at(T![..]) { + return Err(lhs); } + + field_expr::<FLOAT_RECOVERY>(p, lhs) } // test call_expr @@ -455,11 +478,22 @@ fn index_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker { // fn foo() { // x.foo(); // y.bar::<T>(1, 2,); +// x.0.0.call(); +// x.0. call(); // } -fn method_call_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker { - assert!(p.at(T![.]) && p.nth(1) == IDENT && (p.nth(2) == T!['('] || p.nth_at(2, T![::]))); +fn method_call_expr<const FLOAT_RECOVERY: bool>( + p: &mut Parser<'_>, + lhs: CompletedMarker, +) -> CompletedMarker { + if FLOAT_RECOVERY { + assert!(p.nth(0) == IDENT && (p.nth(1) == T!['('] || p.nth_at(1, T![::]))); + } else { + assert!(p.at(T![.]) && p.nth(1) == IDENT && (p.nth(2) == T!['('] || p.nth_at(2, T![::]))); + } let m = lhs.precede(p); - p.bump_any(); + if !FLOAT_RECOVERY { + p.bump(T![.]); + } name_ref(p); generic_args::opt_generic_arg_list(p, true); if p.at(T!['(']) { @@ -472,21 +506,35 @@ fn method_call_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker // fn foo() { // x.foo; // x.0.bar; +// x.0.1; +// x.0. bar; // x.0(); // } -fn field_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker { - assert!(p.at(T![.])); +fn field_expr<const FLOAT_RECOVERY: bool>( + p: &mut Parser<'_>, + lhs: CompletedMarker, +) -> Result<CompletedMarker, CompletedMarker> { + if !FLOAT_RECOVERY { + assert!(p.at(T![.])); + } let m = lhs.precede(p); - p.bump(T![.]); + if !FLOAT_RECOVERY { + p.bump(T![.]); + } if p.at(IDENT) || p.at(INT_NUMBER) { name_ref_or_index(p); } else if p.at(FLOAT_NUMBER) { - // FIXME: How to recover and instead parse INT + T![.]? - p.bump_any(); + return match p.split_float(m) { + (true, m) => { + let lhs = m.complete(p, FIELD_EXPR); + postfix_dot_expr::<true>(p, lhs) + } + (false, m) => Ok(m.complete(p, FIELD_EXPR)), + }; } else { p.error("expected field name or number"); } - m.complete(p, FIELD_EXPR) + Ok(m.complete(p, FIELD_EXPR)) } // test try_expr @@ -517,23 +565,20 @@ fn cast_expr(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker { m.complete(p, CAST_EXPR) } +// test_err arg_list_recovery +// fn main() { +// foo(bar::); +// foo(bar:); +// foo(bar+); +// } fn arg_list(p: &mut Parser<'_>) { assert!(p.at(T!['('])); let m = p.start(); - p.bump(T!['(']); - while !p.at(T![')']) && !p.at(EOF) { - // test arg_with_attr - // fn main() { - // foo(#[attr] 92) - // } - if !expr(p) { - break; - } - if !p.at(T![')']) && !p.expect(T![,]) { - break; - } - } - p.eat(T![')']); + // test arg_with_attr + // fn main() { + // foo(#[attr] 92) + // } + delimited(p, T!['('], T![')'], T![,], EXPR_FIRST.union(ATTRIBUTE_FIRST), expr); m.complete(p, ARG_LIST); } diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs index efa399735..efc260383 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs @@ -40,26 +40,28 @@ pub(super) const ATOM_EXPR_FIRST: TokenSet = T!['{'], T!['['], T![|], - T![move], + T![async], T![box], + T![break], + T![const], + T![continue], + T![do], + T![for], T![if], - T![while], + T![let], + T![loop], T![match], - T![unsafe], + T![move], T![return], - T![yield], - T![do], - T![break], - T![continue], - T![async], + T![static], T![try], - T![const], - T![loop], - T![for], + T![unsafe], + T![while], + T![yield], LIFETIME_IDENT, ])); -const EXPR_RECOVERY_SET: TokenSet = TokenSet::new(&[T![let]]); +pub(super) const EXPR_RECOVERY_SET: TokenSet = TokenSet::new(&[T![')'], T![']']]); pub(super) fn atom_expr( p: &mut Parser<'_>, @@ -116,7 +118,7 @@ pub(super) fn atom_expr( // fn main() { // 'loop: impl // } - p.error("expected a loop"); + p.error("expected a loop or block"); m.complete(p, ERROR); return None; } @@ -152,12 +154,12 @@ pub(super) fn atom_expr( m.complete(p, BLOCK_EXPR) } - T![static] | T![async] | T![move] | T![|] => closure_expr(p), + T![const] | T![static] | T![async] | T![move] | T![|] => closure_expr(p), T![for] if la == T![<] => closure_expr(p), T![for] => for_expr(p, None), _ => { - p.err_recover("expected expression", EXPR_RECOVERY_SET); + p.err_and_bump("expected expression"); return None; } }; @@ -255,7 +257,7 @@ fn array_expr(p: &mut Parser<'_>) -> CompletedMarker { // } fn closure_expr(p: &mut Parser<'_>) -> CompletedMarker { assert!(match p.current() { - T![static] | T![async] | T![move] | T![|] => true, + T![const] | T![static] | T![async] | T![move] | T![|] => true, T![for] => p.nth(1) == T![<], _ => false, }); @@ -265,7 +267,9 @@ fn closure_expr(p: &mut Parser<'_>) -> CompletedMarker { if p.at(T![for]) { types::for_binder(p); } - + // test const_closure + // fn main() { let cl = const || _ = 0; } + p.eat(T![const]); p.eat(T![static]); p.eat(T![async]); p.eat(T![move]); diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_args.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_args.rs index c438943a0..919d9b91e 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_args.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_args.rs @@ -5,27 +5,35 @@ pub(super) fn opt_generic_arg_list(p: &mut Parser<'_>, colon_colon_required: boo if p.at(T![::]) && p.nth(2) == T![<] { m = p.start(); p.bump(T![::]); - p.bump(T![<]); } else if !colon_colon_required && p.at(T![<]) && p.nth(1) != T![=] { m = p.start(); - p.bump(T![<]); } else { return; } - while !p.at(EOF) && !p.at(T![>]) { - generic_arg(p); - if !p.at(T![>]) && !p.expect(T![,]) { - break; - } - } - p.expect(T![>]); + delimited(p, T![<], T![>], T![,], GENERIC_ARG_FIRST, generic_arg); m.complete(p, GENERIC_ARG_LIST); } +const GENERIC_ARG_FIRST: TokenSet = TokenSet::new(&[ + LIFETIME_IDENT, + IDENT, + T!['{'], + T![true], + T![false], + T![-], + INT_NUMBER, + FLOAT_NUMBER, + CHAR, + BYTE, + STRING, + BYTE_STRING, +]) +.union(types::TYPE_FIRST); + // test generic_arg // type T = S<i32>; -fn generic_arg(p: &mut Parser<'_>) { +fn generic_arg(p: &mut Parser<'_>) -> bool { match p.current() { LIFETIME_IDENT => lifetime_arg(p), T!['{'] | T![true] | T![false] | T![-] => const_arg(p), @@ -68,8 +76,10 @@ fn generic_arg(p: &mut Parser<'_>) { } } } - _ => type_arg(p), + _ if p.at_ts(types::TYPE_FIRST) => type_arg(p), + _ => return false, } + true } // test lifetime_arg diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs index 6db28ef13..7fcf938ba 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/generic_params.rs @@ -1,3 +1,5 @@ +use crate::grammar::attributes::ATTRIBUTE_FIRST; + use super::*; pub(super) fn opt_generic_param_list(p: &mut Parser<'_>) { @@ -11,32 +13,31 @@ pub(super) fn opt_generic_param_list(p: &mut Parser<'_>) { fn generic_param_list(p: &mut Parser<'_>) { assert!(p.at(T![<])); let m = p.start(); - p.bump(T![<]); + delimited(p, T![<], T![>], T![,], GENERIC_PARAM_FIRST.union(ATTRIBUTE_FIRST), |p| { + // test generic_param_attribute + // fn foo<#[lt_attr] 'a, #[t_attr] T>() {} + let m = p.start(); + attributes::outer_attrs(p); + generic_param(p, m) + }); - while !p.at(EOF) && !p.at(T![>]) { - generic_param(p); - if !p.at(T![>]) && !p.expect(T![,]) { - break; - } - } - p.expect(T![>]); m.complete(p, GENERIC_PARAM_LIST); } -fn generic_param(p: &mut Parser<'_>) { - let m = p.start(); - // test generic_param_attribute - // fn foo<#[lt_attr] 'a, #[t_attr] T>() {} - attributes::outer_attrs(p); +const GENERIC_PARAM_FIRST: TokenSet = TokenSet::new(&[IDENT, LIFETIME_IDENT, T![const]]); + +fn generic_param(p: &mut Parser<'_>, m: Marker) -> bool { match p.current() { LIFETIME_IDENT => lifetime_param(p, m), IDENT => type_param(p, m), T![const] => const_param(p, m), _ => { m.abandon(p); - p.err_and_bump("expected type parameter"); + p.err_and_bump("expected generic parameter"); + return false; } } + true } // test lifetime_param diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/items/adt.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/items/adt.rs index e7d30516b..17f41b8e1 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/items/adt.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/items/adt.rs @@ -1,3 +1,5 @@ +use crate::grammar::attributes::ATTRIBUTE_FIRST; + use super::*; // test struct_item @@ -141,28 +143,31 @@ pub(crate) fn record_field_list(p: &mut Parser<'_>) { } } +const TUPLE_FIELD_FIRST: TokenSet = + types::TYPE_FIRST.union(ATTRIBUTE_FIRST).union(VISIBILITY_FIRST); + fn tuple_field_list(p: &mut Parser<'_>) { assert!(p.at(T!['('])); let m = p.start(); - p.bump(T!['(']); - while !p.at(T![')']) && !p.at(EOF) { + delimited(p, T!['('], T![')'], T![,], TUPLE_FIELD_FIRST, |p| { let m = p.start(); // test tuple_field_attrs // struct S (#[attr] f32); attributes::outer_attrs(p); - opt_visibility(p, true); + let has_vis = opt_visibility(p, true); if !p.at_ts(types::TYPE_FIRST) { p.error("expected a type"); - m.complete(p, ERROR); - break; + if has_vis { + m.complete(p, ERROR); + } else { + m.abandon(p); + } + return false; } types::type_(p); m.complete(p, TUPLE_FIELD); + true + }); - if !p.at(T![')']) { - p.expect(T![,]); - } - } - p.expect(T![')']); m.complete(p, TUPLE_FIELD_LIST); } diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs index 20e8e95f0..74eae9151 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/params.rs @@ -1,3 +1,5 @@ +use crate::grammar::attributes::ATTRIBUTE_FIRST; + use super::*; // test param_list @@ -66,14 +68,20 @@ fn list_(p: &mut Parser<'_>, flavor: Flavor) { } }; - if !p.at_ts(PARAM_FIRST) { + if !p.at_ts(PARAM_FIRST.union(ATTRIBUTE_FIRST)) { p.error("expected value parameter"); m.abandon(p); break; } param(p, m, flavor); - if !p.at(ket) { - p.expect(T![,]); + if !p.at(T![,]) { + if p.at_ts(PARAM_FIRST.union(ATTRIBUTE_FIRST)) { + p.error("expected `,`"); + } else { + break; + } + } else { + p.bump(T![,]); } } diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs index af3b6f63c..1064ae997 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs @@ -67,6 +67,10 @@ fn path_for_qualifier( } } +const EXPR_PATH_SEGMENT_RECOVERY_SET: TokenSet = + items::ITEM_RECOVERY_SET.union(TokenSet::new(&[T![')'], T![,], T![let]])); +const TYPE_PATH_SEGMENT_RECOVERY_SET: TokenSet = types::TYPE_RECOVERY_SET; + fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) { let m = p.start(); // test qual_paths @@ -102,7 +106,12 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) { m.complete(p, NAME_REF); } _ => { - p.err_recover("expected identifier", items::ITEM_RECOVERY_SET); + let recover_set = match mode { + Mode::Use => items::ITEM_RECOVERY_SET, + Mode::Type => TYPE_PATH_SEGMENT_RECOVERY_SET, + Mode::Expr => EXPR_PATH_SEGMENT_RECOVERY_SET, + }; + p.err_recover("expected identifier", recover_set); if empty { // test_err empty_segment // use crate::; diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs index 5c6e18fee..7d0b156c5 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs @@ -17,8 +17,9 @@ pub(super) const TYPE_FIRST: TokenSet = paths::PATH_FIRST.union(TokenSet::new(&[ T![Self], ])); -const TYPE_RECOVERY_SET: TokenSet = TokenSet::new(&[ +pub(super) const TYPE_RECOVERY_SET: TokenSet = TokenSet::new(&[ T![')'], + T![>], T![,], // test_err struct_field_recover // struct S { f pub g: () } diff --git a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs index b48921f19..100deff46 100644 --- a/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs +++ b/src/tools/rust-analyzer/crates/parser/src/lexed_str.rs @@ -82,6 +82,7 @@ impl<'a> LexedStr<'a> { pub fn text(&self, i: usize) -> &str { self.range_text(i..i + 1) } + pub fn range_text(&self, r: ops::Range<usize>) -> &str { assert!(r.start < r.end && r.end <= self.len()); let lo = self.start[r.start] as usize; @@ -216,6 +217,10 @@ impl<'a> Converter<'a> { rustc_lexer::TokenKind::Caret => T![^], rustc_lexer::TokenKind::Percent => T![%], rustc_lexer::TokenKind::Unknown => ERROR, + rustc_lexer::TokenKind::UnknownPrefix => { + err = "unknown literal prefix"; + IDENT + } } }; diff --git a/src/tools/rust-analyzer/crates/parser/src/lib.rs b/src/tools/rust-analyzer/crates/parser/src/lib.rs index 87be47927..8c5aed023 100644 --- a/src/tools/rust-analyzer/crates/parser/src/lib.rs +++ b/src/tools/rust-analyzer/crates/parser/src/lib.rs @@ -102,10 +102,14 @@ impl TopEntryPoint { match step { Step::Enter { .. } => depth += 1, Step::Exit => depth -= 1, + Step::FloatSplit { ends_in_dot: has_pseudo_dot } => { + depth -= 1 + !has_pseudo_dot as usize + } Step::Token { .. } | Step::Error { .. } => (), } } assert!(!first, "no tree at all"); + assert_eq!(depth, 0, "unbalanced tree"); } res diff --git a/src/tools/rust-analyzer/crates/parser/src/output.rs b/src/tools/rust-analyzer/crates/parser/src/output.rs index 6ca841cfe..41d4c68b2 100644 --- a/src/tools/rust-analyzer/crates/parser/src/output.rs +++ b/src/tools/rust-analyzer/crates/parser/src/output.rs @@ -25,53 +25,88 @@ pub struct Output { #[derive(Debug)] pub enum Step<'a> { Token { kind: SyntaxKind, n_input_tokens: u8 }, + FloatSplit { ends_in_dot: bool }, Enter { kind: SyntaxKind }, Exit, Error { msg: &'a str }, } impl Output { + const EVENT_MASK: u32 = 0b1; + const TAG_MASK: u32 = 0x0000_00F0; + const N_INPUT_TOKEN_MASK: u32 = 0x0000_FF00; + const KIND_MASK: u32 = 0xFFFF_0000; + + const ERROR_SHIFT: u32 = Self::EVENT_MASK.trailing_ones(); + const TAG_SHIFT: u32 = Self::TAG_MASK.trailing_zeros(); + const N_INPUT_TOKEN_SHIFT: u32 = Self::N_INPUT_TOKEN_MASK.trailing_zeros(); + const KIND_SHIFT: u32 = Self::KIND_MASK.trailing_zeros(); + + const TOKEN_EVENT: u8 = 0; + const ENTER_EVENT: u8 = 1; + const EXIT_EVENT: u8 = 2; + const SPLIT_EVENT: u8 = 3; + pub fn iter(&self) -> impl Iterator<Item = Step<'_>> { self.event.iter().map(|&event| { - if event & 0b1 == 0 { - return Step::Error { msg: self.error[(event as usize) >> 1].as_str() }; + if event & Self::EVENT_MASK == 0 { + return Step::Error { + msg: self.error[(event as usize) >> Self::ERROR_SHIFT].as_str(), + }; } - let tag = ((event & 0x0000_00F0) >> 4) as u8; + let tag = ((event & Self::TAG_MASK) >> Self::TAG_SHIFT) as u8; match tag { - 0 => { - let kind: SyntaxKind = (((event & 0xFFFF_0000) >> 16) as u16).into(); - let n_input_tokens = ((event & 0x0000_FF00) >> 8) as u8; + Self::TOKEN_EVENT => { + let kind: SyntaxKind = + (((event & Self::KIND_MASK) >> Self::KIND_SHIFT) as u16).into(); + let n_input_tokens = + ((event & Self::N_INPUT_TOKEN_MASK) >> Self::N_INPUT_TOKEN_SHIFT) as u8; Step::Token { kind, n_input_tokens } } - 1 => { - let kind: SyntaxKind = (((event & 0xFFFF_0000) >> 16) as u16).into(); + Self::ENTER_EVENT => { + let kind: SyntaxKind = + (((event & Self::KIND_MASK) >> Self::KIND_SHIFT) as u16).into(); Step::Enter { kind } } - 2 => Step::Exit, + Self::EXIT_EVENT => Step::Exit, + Self::SPLIT_EVENT => { + Step::FloatSplit { ends_in_dot: event & Self::N_INPUT_TOKEN_MASK != 0 } + } _ => unreachable!(), } }) } pub(crate) fn token(&mut self, kind: SyntaxKind, n_tokens: u8) { - let e = ((kind as u16 as u32) << 16) | ((n_tokens as u32) << 8) | 1; + let e = ((kind as u16 as u32) << Self::KIND_SHIFT) + | ((n_tokens as u32) << Self::N_INPUT_TOKEN_SHIFT) + | Self::EVENT_MASK; self.event.push(e) } + pub(crate) fn float_split_hack(&mut self, ends_in_dot: bool) { + let e = (Self::SPLIT_EVENT as u32) << Self::TAG_SHIFT + | ((ends_in_dot as u32) << Self::N_INPUT_TOKEN_SHIFT) + | Self::EVENT_MASK; + self.event.push(e); + } + pub(crate) fn enter_node(&mut self, kind: SyntaxKind) { - let e = ((kind as u16 as u32) << 16) | (1 << 4) | 1; + let e = ((kind as u16 as u32) << Self::KIND_SHIFT) + | ((Self::ENTER_EVENT as u32) << Self::TAG_SHIFT) + | Self::EVENT_MASK; self.event.push(e) } pub(crate) fn leave_node(&mut self) { - let e = 2 << 4 | 1; + let e = (Self::EXIT_EVENT as u32) << Self::TAG_SHIFT | Self::EVENT_MASK; self.event.push(e) } pub(crate) fn error(&mut self, error: String) { let idx = self.error.len(); self.error.push(error); - let e = (idx as u32) << 1; + let e = (idx as u32) << Self::ERROR_SHIFT; self.event.push(e); } } diff --git a/src/tools/rust-analyzer/crates/parser/src/parser.rs b/src/tools/rust-analyzer/crates/parser/src/parser.rs index 48aecb35b..280416ae7 100644 --- a/src/tools/rust-analyzer/crates/parser/src/parser.rs +++ b/src/tools/rust-analyzer/crates/parser/src/parser.rs @@ -181,6 +181,35 @@ impl<'t> Parser<'t> { self.do_bump(kind, 1); } + /// Advances the parser by one token + pub(crate) fn split_float(&mut self, mut marker: Marker) -> (bool, Marker) { + assert!(self.at(SyntaxKind::FLOAT_NUMBER)); + // we have parse `<something>.` + // `<something>`.0.1 + // here we need to insert an extra event + // + // `<something>`. 0. 1; + // here we need to change the follow up parse, the return value will cause us to emulate a dot + // the actual splitting happens later + let ends_in_dot = !self.inp.is_joint(self.pos); + if !ends_in_dot { + let new_marker = self.start(); + let idx = marker.pos as usize; + match &mut self.events[idx] { + Event::Start { forward_parent, kind } => { + *kind = SyntaxKind::FIELD_EXPR; + *forward_parent = Some(new_marker.pos - marker.pos); + } + _ => unreachable!(), + } + marker.bomb.defuse(); + marker = new_marker; + }; + self.pos += 1 as usize; + self.push_event(Event::FloatSplitHack { ends_in_dot }); + (ends_in_dot, marker) + } + /// Advances the parser by one token, remapping its kind. /// This is useful to create contextual keywords from /// identifiers. For example, the lexer creates a `union` diff --git a/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs b/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs index 2be4050d1..47e4adcbb 100644 --- a/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs +++ b/src/tools/rust-analyzer/crates/parser/src/shortcuts.rs @@ -43,7 +43,16 @@ impl<'a> LexedStr<'a> { res.was_joint(); } res.push(kind); + // Tag the token as joint if it is float with a fractional part + // we use this jointness to inform the parser about what token split + // event to emit when we encounter a float literal in a field access + if kind == SyntaxKind::FLOAT_NUMBER { + if !self.text(i).ends_with('.') { + res.was_joint(); + } + } } + was_joint = true; } } @@ -63,6 +72,9 @@ impl<'a> LexedStr<'a> { Step::Token { kind, n_input_tokens: n_raw_tokens } => { builder.token(kind, n_raw_tokens) } + Step::FloatSplit { ends_in_dot: has_pseudo_dot } => { + builder.float_split(has_pseudo_dot) + } Step::Enter { kind } => builder.enter(kind), Step::Exit => builder.exit(), Step::Error { msg } => { @@ -109,6 +121,16 @@ impl Builder<'_, '_> { self.do_token(kind, n_tokens as usize); } + fn float_split(&mut self, has_pseudo_dot: bool) { + match mem::replace(&mut self.state, State::Normal) { + State::PendingEnter => unreachable!(), + State::PendingExit => (self.sink)(StrStep::Exit), + State::Normal => (), + } + self.eat_trivias(); + self.do_float_split(has_pseudo_dot); + } + fn enter(&mut self, kind: SyntaxKind) { match mem::replace(&mut self.state, State::Normal) { State::PendingEnter => { @@ -164,6 +186,37 @@ impl Builder<'_, '_> { self.pos += n_tokens; (self.sink)(StrStep::Token { kind, text }); } + + fn do_float_split(&mut self, has_pseudo_dot: bool) { + let text = &self.lexed.range_text(self.pos..self.pos + 1); + self.pos += 1; + match text.split_once('.') { + Some((left, right)) => { + assert!(!left.is_empty()); + (self.sink)(StrStep::Enter { kind: SyntaxKind::NAME_REF }); + (self.sink)(StrStep::Token { kind: SyntaxKind::INT_NUMBER, text: left }); + (self.sink)(StrStep::Exit); + + // here we move the exit up, the original exit has been deleted in process + (self.sink)(StrStep::Exit); + + (self.sink)(StrStep::Token { kind: SyntaxKind::DOT, text: "." }); + + if has_pseudo_dot { + assert!(right.is_empty(), "{left}.{right}"); + self.state = State::Normal; + } else { + (self.sink)(StrStep::Enter { kind: SyntaxKind::NAME_REF }); + (self.sink)(StrStep::Token { kind: SyntaxKind::INT_NUMBER, text: right }); + (self.sink)(StrStep::Exit); + + // the parser creates an unbalanced start node, we are required to close it here + self.state = State::PendingExit; + } + } + None => unreachable!(), + } + } } fn n_attached_trivias<'a>( diff --git a/src/tools/rust-analyzer/crates/parser/src/tests.rs b/src/tools/rust-analyzer/crates/parser/src/tests.rs index c1b4e9a7d..2fec765bd 100644 --- a/src/tools/rust-analyzer/crates/parser/src/tests.rs +++ b/src/tools/rust-analyzer/crates/parser/src/tests.rs @@ -15,6 +15,7 @@ use crate::{LexedStr, TopEntryPoint}; #[test] fn lex_ok() { for case in TestCase::list("lexer/ok") { + let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); let actual = lex(&case.text); expect_file![case.rast].assert_eq(&actual) } @@ -23,6 +24,7 @@ fn lex_ok() { #[test] fn lex_err() { for case in TestCase::list("lexer/err") { + let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); let actual = lex(&case.text); expect_file![case.rast].assert_eq(&actual) } @@ -46,6 +48,7 @@ fn lex(text: &str) -> String { #[test] fn parse_ok() { for case in TestCase::list("parser/ok") { + let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text); assert!(!errors, "errors in an OK file {}:\n{actual}", case.rs.display()); expect_file![case.rast].assert_eq(&actual); @@ -55,6 +58,7 @@ fn parse_ok() { #[test] fn parse_inline_ok() { for case in TestCase::list("parser/inline/ok") { + let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text); assert!(!errors, "errors in an OK file {}:\n{actual}", case.rs.display()); expect_file![case.rast].assert_eq(&actual); @@ -64,6 +68,7 @@ fn parse_inline_ok() { #[test] fn parse_err() { for case in TestCase::list("parser/err") { + let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text); assert!(errors, "no errors in an ERR file {}:\n{actual}", case.rs.display()); expect_file![case.rast].assert_eq(&actual) @@ -73,6 +78,7 @@ fn parse_err() { #[test] fn parse_inline_err() { for case in TestCase::list("parser/inline/err") { + let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text); assert!(errors, "no errors in an ERR file {}:\n{actual}", case.rs.display()); expect_file![case.rast].assert_eq(&actual) diff --git a/src/tools/rust-analyzer/crates/parser/src/tests/prefix_entries.rs b/src/tools/rust-analyzer/crates/parser/src/tests/prefix_entries.rs index e626b4f27..40f92e588 100644 --- a/src/tools/rust-analyzer/crates/parser/src/tests/prefix_entries.rs +++ b/src/tools/rust-analyzer/crates/parser/src/tests/prefix_entries.rs @@ -51,6 +51,9 @@ fn expr() { check(PrefixEntryPoint::Expr, "-1", "-1"); check(PrefixEntryPoint::Expr, "fn foo() {}", "fn"); check(PrefixEntryPoint::Expr, "#[attr] ()", "#[attr] ()"); + check(PrefixEntryPoint::Expr, "foo.0", "foo.0"); + check(PrefixEntryPoint::Expr, "foo.0.1", "foo.0.1"); + check(PrefixEntryPoint::Expr, "foo.0. foo", "foo.0. foo"); } #[test] @@ -88,6 +91,7 @@ fn check(entry: PrefixEntryPoint, input: &str, prefix: &str) { for step in entry.parse(&input).iter() { match step { Step::Token { n_input_tokens, .. } => n_tokens += n_input_tokens as usize, + Step::FloatSplit { .. } => n_tokens += 1, Step::Enter { .. } | Step::Exit | Step::Error { .. } => (), } } diff --git a/src/tools/rust-analyzer/crates/parser/src/tests/top_entries.rs b/src/tools/rust-analyzer/crates/parser/src/tests/top_entries.rs index eb640dc7f..49dd9e293 100644 --- a/src/tools/rust-analyzer/crates/parser/src/tests/top_entries.rs +++ b/src/tools/rust-analyzer/crates/parser/src/tests/top_entries.rs @@ -65,7 +65,7 @@ fn macro_stmt() { MACRO_STMTS ERROR SHEBANG "#!/usr/bin/rust" - error 0: expected expression + error 0: expected expression, item or let statement "##]], ); check( diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast index a01543217..cdc01863a 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0009_broken_struct_type_parameter.rast @@ -44,8 +44,7 @@ SOURCE_FILE IDENT "T" SEMICOLON ";" WHITESPACE "\n" -error 9: expected type parameter -error 11: expected COMMA +error 9: expected generic parameter error 11: expected R_ANGLE error 11: expected `;`, `{`, or `(` error 12: expected an item diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0013_invalid_type.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0013_invalid_type.rast index eec84a0c6..b485c71ab 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0013_invalid_type.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0013_invalid_type.rast @@ -43,17 +43,14 @@ SOURCE_FILE IDENT "Box" GENERIC_ARG_LIST L_ANGLE "<" - TYPE_ARG - ERROR - AT "@" - WHITESPACE " " - TUPLE_FIELD - PATH_TYPE - PATH - PATH_SEGMENT - NAME_REF - IDENT "Any" - ERROR + ERROR + AT "@" + WHITESPACE " " + MACRO_CALL + PATH + PATH_SEGMENT + NAME_REF + IDENT "Any" ERROR R_ANGLE ">" ERROR @@ -69,17 +66,14 @@ SOURCE_FILE ERROR SEMICOLON ";" WHITESPACE "\n\n" -error 67: expected type -error 68: expected COMMA -error 68: expected R_ANGLE -error 68: expected COMMA -error 68: expected R_ANGLE -error 68: expected COMMA -error 68: expected R_ANGLE -error 68: expected COMMA -error 72: expected COMMA -error 72: expected a type -error 72: expected R_PAREN +error 67: expected R_ANGLE +error 67: expected R_ANGLE +error 67: expected R_ANGLE +error 67: expected R_PAREN +error 67: expected SEMICOLON +error 67: expected an item +error 72: expected BANG +error 72: expected `{`, `[`, `(` error 72: expected SEMICOLON error 72: expected an item error 73: expected an item diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0022_bad_exprs.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0022_bad_exprs.rast index 900394bd9..d97fc6c72 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0022_bad_exprs.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0022_bad_exprs.rast @@ -145,27 +145,29 @@ SOURCE_FILE error 16: expected expression error 17: expected R_BRACK error 17: expected SEMICOLON -error 17: expected expression +error 17: expected expression, item or let statement error 25: expected a name error 26: expected `;`, `{`, or `(` error 30: expected pattern error 31: expected SEMICOLON error 53: expected expression +error 54: expected R_PAREN error 54: expected SEMICOLON -error 54: expected expression +error 54: expected expression, item or let statement error 60: expected type error 60: expected `{` -error 60: expected expression +error 60: expected expression, item or let statement error 65: expected pattern error 65: expected SEMICOLON -error 65: expected expression +error 65: expected expression, item or let statement error 92: expected expression +error 93: expected R_PAREN error 93: expected SEMICOLON -error 93: expected expression -error 95: expected expression -error 96: expected expression +error 93: expected expression, item or let statement +error 95: expected expression, item or let statement +error 96: expected expression, item or let statement error 103: expected a name error 104: expected `{` error 108: expected pattern error 108: expected SEMICOLON -error 108: expected expression +error 108: expected expression, item or let statement diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast index d374f8661..f0dbc9b10 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0024_many_type_parens.rast @@ -168,75 +168,21 @@ SOURCE_FILE L_PAREN "(" ERROR QUESTION "?" - EXPR_STMT - PATH_EXPR - PATH - PATH_SEGMENT - NAME_REF - IDENT "Sized" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Sized" ERROR R_PAREN ")" WHITESPACE " " ERROR PLUS "+" WHITESPACE " " - TUPLE_EXPR - L_PAREN "(" - CLOSURE_EXPR - FOR_KW "for" - GENERIC_PARAM_LIST - L_ANGLE "<" - LIFETIME_PARAM - LIFETIME - LIFETIME_IDENT "'a" - R_ANGLE ">" - WHITESPACE " " - BIN_EXPR - BIN_EXPR - BIN_EXPR - BIN_EXPR - PATH_EXPR - PATH - PATH_SEGMENT - NAME_REF - IDENT "Trait" - L_ANGLE "<" - ERROR - LIFETIME_IDENT "'a" - R_ANGLE ">" - ERROR - R_PAREN ")" - WHITESPACE " " - PLUS "+" - WHITESPACE " " - PAREN_EXPR - L_PAREN "(" - PATH_EXPR - PATH - PATH_SEGMENT - NAME_REF - IDENT "Copy" - R_PAREN ")" - R_ANGLE ">" - ERROR - SEMICOLON ";" - WHITESPACE "\n " - LET_EXPR - LET_KW "let" - WHITESPACE " " - WILDCARD_PAT - UNDERSCORE "_" - ERROR - COLON ":" - WHITESPACE " " + EXPR_STMT BIN_EXPR BIN_EXPR - PATH_EXPR - PATH - PATH_SEGMENT - NAME_REF - IDENT "Box" - L_ANGLE "<" TUPLE_EXPR L_PAREN "(" CLOSURE_EXPR @@ -250,78 +196,117 @@ SOURCE_FILE WHITESPACE " " BIN_EXPR BIN_EXPR - BIN_EXPR - BIN_EXPR - PATH_EXPR - PATH - PATH_SEGMENT - NAME_REF - IDENT "Trait" - L_ANGLE "<" - ERROR - LIFETIME_IDENT "'a" - R_ANGLE ">" - ERROR - R_PAREN ")" - WHITESPACE " " - PLUS "+" - WHITESPACE " " - PAREN_EXPR - L_PAREN "(" - PATH_EXPR - PATH - PATH_SEGMENT - NAME_REF - IDENT "Copy" - R_PAREN ")" - WHITESPACE " " - PLUS "+" - WHITESPACE " " - PAREN_EXPR - L_PAREN "(" + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "Trait" + L_ANGLE "<" ERROR - QUESTION "?" + LIFETIME_IDENT "'a" + R_ANGLE ">" + R_PAREN ")" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + PAREN_EXPR + L_PAREN "(" PATH_EXPR PATH PATH_SEGMENT NAME_REF - IDENT "Sized" + IDENT "Copy" R_PAREN ")" R_ANGLE ">" ERROR SEMICOLON ";" + WHITESPACE "\n " + LET_STMT + LET_KW "let" + WHITESPACE " " + WILDCARD_PAT + UNDERSCORE "_" + COLON ":" + WHITESPACE " " + DYN_TRAIT_TYPE + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Box" + GENERIC_ARG_LIST + L_ANGLE "<" + TYPE_ARG + PAREN_TYPE + L_PAREN "(" + FOR_TYPE + FOR_KW "for" + GENERIC_PARAM_LIST + L_ANGLE "<" + LIFETIME_PARAM + LIFETIME + LIFETIME_IDENT "'a" + R_ANGLE ">" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Trait" + GENERIC_ARG_LIST + L_ANGLE "<" + LIFETIME_ARG + LIFETIME + LIFETIME_IDENT "'a" + R_ANGLE ">" + R_PAREN ")" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + TYPE_BOUND + L_PAREN "(" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Copy" + R_PAREN ")" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + TYPE_BOUND + L_PAREN "(" + QUESTION "?" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Sized" + R_PAREN ")" + ERROR + R_ANGLE ">" + SEMICOLON ";" WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" -error 88: expected COMMA error 88: expected R_ANGLE error 121: expected SEMICOLON -error 121: expected expression +error 121: expected expression, item or let statement error 140: expected type error 141: expected R_PAREN error 141: expected COMMA -error 141: expected R_ANGLE -error 141: expected SEMICOLON +error 146: expected R_ANGLE error 146: expected SEMICOLON -error 146: expected expression -error 148: expected expression +error 146: expected expression, item or let statement +error 148: expected expression, item or let statement error 158: expected `|` error 158: expected COMMA error 165: expected expression error 168: expected expression error 179: expected expression -error 180: expected COMMA -error 190: expected EQ -error 190: expected expression -error 191: expected COMMA -error 204: expected `|` -error 204: expected COMMA -error 211: expected expression -error 214: expected expression -error 228: expected expression -error 229: expected R_PAREN -error 229: expected COMMA -error 236: expected expression -error 237: expected COMMA -error 237: expected expression -error 237: expected R_PAREN +error 180: expected SEMICOLON +error 215: expected R_ANGLE +error 235: expected SEMICOLON +error 235: expected expression, item or let statement diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0025_nope.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0025_nope.rast index 6b49724ec..b6bc00883 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0025_nope.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0025_nope.rast @@ -156,8 +156,7 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "i32" - WHITESPACE " " - ERROR + WHITESPACE " " ERROR L_CURLY "{" R_CURLY "}" @@ -199,10 +198,8 @@ error 95: expected type error 95: expected COMMA error 96: expected field error 98: expected field declaration +error 371: expected R_PAREN error 371: expected COMMA -error 372: expected a type -error 372: expected R_PAREN -error 372: expected COMMA error 372: expected enum variant error 374: expected enum variant error 494: expected pattern diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0042_weird_blocks.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0042_weird_blocks.rast index 9cea337ce..1cdc6e6e7 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0042_weird_blocks.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0042_weird_blocks.rast @@ -72,4 +72,4 @@ SOURCE_FILE error 24: expected existential, fn, trait or impl error 41: expected existential, fn, trait or impl error 56: expected a block -error 75: expected a loop +error 75: expected a loop or block diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0048_double_fish.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0048_double_fish.rast index 3a05bfee1..207a5c24d 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0048_double_fish.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0048_double_fish.rast @@ -12,7 +12,7 @@ SOURCE_FILE STMT_LIST L_CURLY "{" WHITESPACE "\n " - EXPR_STMT + BIN_EXPR PATH_EXPR PATH PATH_SEGMENT @@ -41,13 +41,14 @@ SOURCE_FILE COLON2 "::" ERROR L_ANGLE "<" - BIN_EXPR - PATH_EXPR - PATH - PATH_SEGMENT - NAME_REF - IDENT "nope" - SHR ">>" + TYPE_ARG + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "nope" + R_ANGLE ">" + R_ANGLE ">" ERROR SEMICOLON ";" WHITESPACE "\n" @@ -114,8 +115,6 @@ SOURCE_FILE WHITESPACE "\n" error 30: expected identifier error 31: expected COMMA -error 31: expected R_ANGLE -error 31: expected SEMICOLON error 37: expected expression error 75: expected identifier error 76: expected SEMICOLON diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast index 56cea4b15..ea5203fb9 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0002_misplaced_label_err.rast @@ -23,6 +23,6 @@ SOURCE_FILE WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" -error 22: expected a loop +error 22: expected a loop or block error 27: expected type error 27: expected `{` diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast new file mode 100644 index 000000000..5d0fe859c --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rast @@ -0,0 +1,77 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "main" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + EXPR_STMT + CALL_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "foo" + ARG_LIST + L_PAREN "(" + PATH_EXPR + PATH + PATH + PATH_SEGMENT + NAME_REF + IDENT "bar" + COLON2 "::" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + CALL_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "foo" + ARG_LIST + L_PAREN "(" + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "bar" + ERROR + COLON ":" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + CALL_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "foo" + ARG_LIST + L_PAREN "(" + BIN_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "bar" + PLUS "+" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + R_CURLY "}" + WHITESPACE "\n" +error 25: expected identifier +error 39: expected COMMA +error 39: expected expression +error 55: expected expression diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs new file mode 100644 index 000000000..0e7ac9cc3 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_arg_list_recovery.rs @@ -0,0 +1,5 @@ +fn main() { + foo(bar::); + foo(bar:); + foo(bar+); +} diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast index e72df374d..ea50ad35d 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0015_missing_fn_param_type.rast @@ -49,5 +49,5 @@ SOURCE_FILE R_CURLY "}" WHITESPACE "\n" error 6: missing type for function parameter -error 6: expected COMMA +error 6: expected `,` error 16: missing type for function parameter diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast index 8498724b9..dd27dc489 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast @@ -41,6 +41,39 @@ SOURCE_FILE SEMICOLON ";" WHITESPACE "\n " EXPR_STMT + FIELD_EXPR + FIELD_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "x" + DOT "." + NAME_REF + INT_NUMBER "0" + DOT "." + NAME_REF + INT_NUMBER "1" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + FIELD_EXPR + FIELD_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "x" + DOT "." + NAME_REF + INT_NUMBER "0" + DOT "." + WHITESPACE " " + NAME_REF + IDENT "bar" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT CALL_EXPR FIELD_EXPR PATH_EXPR diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rs index b8da2ddc3..98dbe45a7 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rs +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0011_field_expr.rs @@ -1,5 +1,7 @@ fn foo() { x.foo; x.0.bar; + x.0.1; + x.0. bar; x.0(); } diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rast index dcbcfe123..b28b8eb67 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rast @@ -58,6 +58,49 @@ SOURCE_FILE COMMA "," R_PAREN ")" SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + METHOD_CALL_EXPR + FIELD_EXPR + FIELD_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "x" + DOT "." + NAME_REF + INT_NUMBER "0" + DOT "." + NAME_REF + INT_NUMBER "0" + DOT "." + NAME_REF + IDENT "call" + ARG_LIST + L_PAREN "(" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + METHOD_CALL_EXPR + FIELD_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "x" + DOT "." + NAME_REF + INT_NUMBER "0" + DOT "." + WHITESPACE " " + NAME_REF + IDENT "call" + ARG_LIST + L_PAREN "(" + R_PAREN ")" + SEMICOLON ";" WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rs index 1a3aa35ae..48bb6381e 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rs +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0107_method_call_expr.rs @@ -1,4 +1,6 @@ fn foo() { x.foo(); y.bar::<T>(1, 2,); + x.0.0.call(); + x.0. call(); } diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rast index 9d37ada0d..af713a220 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rast @@ -65,6 +65,41 @@ SOURCE_FILE L_PAREN "(" R_PAREN ")" SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + AWAIT_EXPR + FIELD_EXPR + FIELD_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "x" + DOT "." + NAME_REF + INT_NUMBER "0" + DOT "." + NAME_REF + INT_NUMBER "0" + DOT "." + AWAIT_KW "await" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + AWAIT_EXPR + FIELD_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "x" + DOT "." + NAME_REF + INT_NUMBER "0" + DOT "." + WHITESPACE " " + AWAIT_KW "await" + SEMICOLON ";" WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rs index d2ba89ca6..fe9a3211b 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rs +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0137_await_expr.rs @@ -2,4 +2,6 @@ fn foo() { x.await; x.0.await; x.0().await?.hello(); + x.0.0.await; + x.0. await; } diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0205_const_closure.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0205_const_closure.rast new file mode 100644 index 000000000..06442a1d0 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0205_const_closure.rast @@ -0,0 +1,42 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "main" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE " " + LET_STMT + LET_KW "let" + WHITESPACE " " + IDENT_PAT + NAME + IDENT "cl" + WHITESPACE " " + EQ "=" + WHITESPACE " " + CLOSURE_EXPR + CONST_KW "const" + WHITESPACE " " + PARAM_LIST + PIPE "|" + PIPE "|" + WHITESPACE " " + BIN_EXPR + UNDERSCORE_EXPR + UNDERSCORE "_" + WHITESPACE " " + EQ "=" + WHITESPACE " " + LITERAL + INT_NUMBER "0" + SEMICOLON ";" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0205_const_closure.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0205_const_closure.rs new file mode 100644 index 000000000..0c05cc70b --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0205_const_closure.rs @@ -0,0 +1 @@ +fn main() { let cl = const || _ = 0; } diff --git a/src/tools/rust-analyzer/crates/paths/Cargo.toml b/src/tools/rust-analyzer/crates/paths/Cargo.toml index d23a63d2a..e24e6ecef 100644 --- a/src/tools/rust-analyzer/crates/paths/Cargo.toml +++ b/src/tools/rust-analyzer/crates/paths/Cargo.toml @@ -2,9 +2,11 @@ name = "paths" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml b/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml index f261f3def..28469b832 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml +++ b/src/tools/rust-analyzer/crates/proc-macro-api/Cargo.toml @@ -2,15 +2,17 @@ name = "proc-macro-api" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false [dependencies] -object = { version = "0.29.0", default-features = false, features = [ +object = { version = "0.30.2", default-features = false, features = [ "std", "read_core", "elf", @@ -21,11 +23,12 @@ serde = { version = "1.0.137", features = ["derive"] } serde_json = { version = "1.0.81", features = ["unbounded_depth"] } tracing = "0.1.37" memmap2 = "0.5.4" -snap = "1.0.5" +snap = "1.1.0" -paths = { path = "../paths", version = "0.0.0" } -tt = { path = "../tt", version = "0.0.0" } -stdx = { path = "../stdx", version = "0.0.0" } -profile = { path = "../profile", version = "0.0.0" } +# local deps +paths.workspace = true +tt.workspace = true +stdx.workspace = true +profile.workspace = true # Intentionally *not* depend on anything salsa-related -# base-db = { path = "../base-db", version = "0.0.0" } +# base-db.workspace = true diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs index 7921fda33..90d06967e 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/lib.rs @@ -19,7 +19,8 @@ use std::{ }; use serde::{Deserialize, Serialize}; -use tt::Subtree; + +use ::tt::token_id as tt; use crate::{ msg::{ExpandMacro, FlatTree, PanicMessage}, @@ -70,7 +71,7 @@ impl MacroDylib { /// A handle to a specific macro (a `#[proc_macro]` annotated function). /// -/// It exists withing a context of a specific [`ProcMacroProcess`] -- currently +/// It exists within a context of a specific [`ProcMacroProcess`] -- currently /// we share a single expander process for all macros. #[derive(Debug, Clone)] pub struct ProcMacro { @@ -114,14 +115,14 @@ impl ProcMacroServer { /// Spawns an external process as the proc macro server and returns a client connected to it. pub fn spawn( process_path: AbsPathBuf, - args: impl IntoIterator<Item = impl AsRef<OsStr>>, + args: impl IntoIterator<Item = impl AsRef<OsStr>> + Clone, ) -> io::Result<ProcMacroServer> { let process = ProcMacroProcessSrv::run(process_path, args)?; Ok(ProcMacroServer { process: Arc::new(Mutex::new(process)) }) } pub fn load_dylib(&self, dylib: MacroDylib) -> Result<Vec<ProcMacro>, ServerError> { - let _p = profile::span("ProcMacroClient::by_dylib_path"); + let _p = profile::span("ProcMacroClient::load_dylib"); let macros = self.process.lock().unwrap_or_else(|e| e.into_inner()).find_proc_macros(&dylib.path)?; @@ -151,10 +152,10 @@ impl ProcMacro { pub fn expand( &self, - subtree: &Subtree, - attr: Option<&Subtree>, + subtree: &tt::Subtree, + attr: Option<&tt::Subtree>, env: Vec<(String, String)>, - ) -> Result<Result<Subtree, PanicMessage>, ServerError> { + ) -> Result<Result<tt::Subtree, PanicMessage>, ServerError> { let current_dir = env .iter() .find(|(name, _)| name == "CARGO_MANIFEST_DIR") @@ -173,7 +174,7 @@ impl ProcMacro { let response = self.process.lock().unwrap_or_else(|e| e.into_inner()).send_task(request)?; match response { msg::Response::ExpandMacro(it) => Ok(it.map(FlatTree::to_subtree)), - msg::Response::ListMacros { .. } => { + msg::Response::ListMacros(..) | msg::Response::ApiVersionCheck(..) => { Err(ServerError { message: "unexpected response".to_string(), io: None }) } } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/msg.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/msg.rs index f9c2b9fda..4040efe93 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/msg.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/msg.rs @@ -12,16 +12,21 @@ use crate::ProcMacroKind; pub use crate::msg::flat::FlatTree; +pub const NO_VERSION_CHECK_VERSION: u32 = 0; +pub const CURRENT_API_VERSION: u32 = 1; + #[derive(Debug, Serialize, Deserialize)] pub enum Request { ListMacros { dylib_path: PathBuf }, ExpandMacro(ExpandMacro), + ApiVersionCheck {}, } #[derive(Debug, Serialize, Deserialize)] pub enum Response { ListMacros(Result<Vec<(String, ProcMacroKind)>, String>), ExpandMacro(Result<FlatTree, PanicMessage>), + ApiVersionCheck(u32), } #[derive(Debug, Serialize, Deserialize)] @@ -107,27 +112,31 @@ fn write_json(out: &mut impl Write, msg: &str) -> io::Result<()> { #[cfg(test)] mod tests { use super::*; - use tt::*; + use crate::tt::*; fn fixture_token_tree() -> Subtree { - let mut subtree = Subtree::default(); + let mut subtree = Subtree { delimiter: Delimiter::unspecified(), token_trees: Vec::new() }; subtree .token_trees - .push(TokenTree::Leaf(Ident { text: "struct".into(), id: TokenId(0) }.into())); + .push(TokenTree::Leaf(Ident { text: "struct".into(), span: TokenId(0) }.into())); subtree .token_trees - .push(TokenTree::Leaf(Ident { text: "Foo".into(), id: TokenId(1) }.into())); + .push(TokenTree::Leaf(Ident { text: "Foo".into(), span: TokenId(1) }.into())); subtree.token_trees.push(TokenTree::Leaf(Leaf::Literal(Literal { text: "Foo".into(), - id: TokenId::unspecified(), + span: TokenId::unspecified(), }))); subtree.token_trees.push(TokenTree::Leaf(Leaf::Punct(Punct { char: '@', - id: TokenId::unspecified(), + span: TokenId::unspecified(), spacing: Spacing::Joint, }))); subtree.token_trees.push(TokenTree::Subtree(Subtree { - delimiter: Some(Delimiter { id: TokenId(2), kind: DelimiterKind::Brace }), + delimiter: Delimiter { + open: TokenId(2), + close: TokenId::UNSPECIFIED, + kind: DelimiterKind::Brace, + }, token_trees: vec![], })); subtree diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/msg/flat.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/msg/flat.rs index b178c4626..fd3202e0b 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/msg/flat.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/msg/flat.rs @@ -38,7 +38,8 @@ use std::collections::{HashMap, VecDeque}; use serde::{Deserialize, Serialize}; -use tt::TokenId; + +use crate::tt::{self, TokenId}; #[derive(Serialize, Deserialize, Debug)] pub struct FlatTree { @@ -52,7 +53,7 @@ pub struct FlatTree { struct SubtreeRepr { id: tt::TokenId, - kind: Option<tt::DelimiterKind>, + kind: tt::DelimiterKind, tt: [u32; 2], } @@ -124,19 +125,19 @@ impl FlatTree { impl SubtreeRepr { fn write(self) -> [u32; 4] { let kind = match self.kind { - None => 0, - Some(tt::DelimiterKind::Parenthesis) => 1, - Some(tt::DelimiterKind::Brace) => 2, - Some(tt::DelimiterKind::Bracket) => 3, + tt::DelimiterKind::Invisible => 0, + tt::DelimiterKind::Parenthesis => 1, + tt::DelimiterKind::Brace => 2, + tt::DelimiterKind::Bracket => 3, }; [self.id.0, kind, self.tt[0], self.tt[1]] } fn read([id, kind, lo, len]: [u32; 4]) -> SubtreeRepr { let kind = match kind { - 0 => None, - 1 => Some(tt::DelimiterKind::Parenthesis), - 2 => Some(tt::DelimiterKind::Brace), - 3 => Some(tt::DelimiterKind::Bracket), + 0 => tt::DelimiterKind::Invisible, + 1 => tt::DelimiterKind::Parenthesis, + 2 => tt::DelimiterKind::Brace, + 3 => tt::DelimiterKind::Bracket, other => panic!("bad kind {other}"), }; SubtreeRepr { id: TokenId(id), kind, tt: [lo, len] } @@ -216,7 +217,7 @@ impl<'a> Writer<'a> { tt::Leaf::Literal(lit) => { let idx = self.literal.len() as u32; let text = self.intern(&lit.text); - self.literal.push(LiteralRepr { id: lit.id, text }); + self.literal.push(LiteralRepr { id: lit.span, text }); idx << 2 | 0b01 } tt::Leaf::Punct(punct) => { @@ -224,14 +225,14 @@ impl<'a> Writer<'a> { self.punct.push(PunctRepr { char: punct.char, spacing: punct.spacing, - id: punct.id, + id: punct.span, }); idx << 2 | 0b10 } tt::Leaf::Ident(ident) => { let idx = self.ident.len() as u32; let text = self.intern(&ident.text); - self.ident.push(IdentRepr { id: ident.id, text }); + self.ident.push(IdentRepr { id: ident.span, text }); idx << 2 | 0b11 } }, @@ -243,8 +244,8 @@ impl<'a> Writer<'a> { fn enqueue(&mut self, subtree: &'a tt::Subtree) -> u32 { let idx = self.subtree.len(); - let delimiter_id = subtree.delimiter.map_or(TokenId::unspecified(), |it| it.id); - let delimiter_kind = subtree.delimiter.map(|it| it.kind); + let delimiter_id = subtree.delimiter.open; + let delimiter_kind = subtree.delimiter.kind; self.subtree.push(SubtreeRepr { id: delimiter_id, kind: delimiter_kind, tt: [!0, !0] }); self.work.push_back((idx, subtree)); idx as u32 @@ -276,7 +277,11 @@ impl Reader { let repr = &self.subtree[i]; let token_trees = &self.token_tree[repr.tt[0] as usize..repr.tt[1] as usize]; let s = tt::Subtree { - delimiter: repr.kind.map(|kind| tt::Delimiter { id: repr.id, kind }), + delimiter: tt::Delimiter { + open: repr.id, + close: TokenId::UNSPECIFIED, + kind: repr.kind, + }, token_trees: token_trees .iter() .copied() @@ -291,7 +296,7 @@ impl Reader { let repr = &self.literal[idx]; tt::Leaf::Literal(tt::Literal { text: self.text[repr.text as usize].as_str().into(), - id: repr.id, + span: repr.id, }) .into() } @@ -300,7 +305,7 @@ impl Reader { tt::Leaf::Punct(tt::Punct { char: repr.char, spacing: repr.spacing, - id: repr.id, + span: repr.id, }) .into() } @@ -308,7 +313,7 @@ impl Reader { let repr = &self.ident[idx]; tt::Leaf::Ident(tt::Ident { text: self.text[repr.text as usize].as_str().into(), - id: repr.id, + span: repr.id, }) .into() } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs index 54dcb17f4..1ccbd780f 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/process.rs @@ -10,7 +10,7 @@ use paths::{AbsPath, AbsPathBuf}; use stdx::JodChild; use crate::{ - msg::{Message, Request, Response}, + msg::{Message, Request, Response, CURRENT_API_VERSION}, ProcMacroKind, ServerError, }; @@ -19,19 +19,53 @@ pub(crate) struct ProcMacroProcessSrv { _process: Process, stdin: ChildStdin, stdout: BufReader<ChildStdout>, + version: u32, } impl ProcMacroProcessSrv { pub(crate) fn run( process_path: AbsPathBuf, - args: impl IntoIterator<Item = impl AsRef<OsStr>>, + args: impl IntoIterator<Item = impl AsRef<OsStr>> + Clone, ) -> io::Result<ProcMacroProcessSrv> { - let mut process = Process::run(process_path, args)?; - let (stdin, stdout) = process.stdio().expect("couldn't access child stdio"); + let create_srv = |null_stderr| { + let mut process = Process::run(process_path.clone(), args.clone(), null_stderr)?; + let (stdin, stdout) = process.stdio().expect("couldn't access child stdio"); + + io::Result::Ok(ProcMacroProcessSrv { _process: process, stdin, stdout, version: 0 }) + }; + let mut srv = create_srv(true)?; + tracing::info!("sending version check"); + match srv.version_check() { + Ok(v) if v > CURRENT_API_VERSION => Err(io::Error::new( + io::ErrorKind::Other, + format!( + "proc-macro server's api version ({}) is newer than rust-analyzer's ({})", + v, CURRENT_API_VERSION + ), + )), + Ok(v) => { + tracing::info!("got version {v}"); + srv = create_srv(false)?; + srv.version = v; + Ok(srv) + } + Err(e) => { + tracing::info!(%e, "proc-macro version check failed, restarting and assuming version 0"); + create_srv(false) + } + } + } - let srv = ProcMacroProcessSrv { _process: process, stdin, stdout }; + pub(crate) fn version_check(&mut self) -> Result<u32, ServerError> { + let request = Request::ApiVersionCheck {}; + let response = self.send_task(request)?; - Ok(srv) + match response { + Response::ApiVersionCheck(version) => Ok(version), + Response::ExpandMacro { .. } | Response::ListMacros { .. } => { + Err(ServerError { message: "unexpected response".to_string(), io: None }) + } + } } pub(crate) fn find_proc_macros( @@ -44,7 +78,7 @@ impl ProcMacroProcessSrv { match response { Response::ListMacros(it) => Ok(it), - Response::ExpandMacro { .. } => { + Response::ExpandMacro { .. } | Response::ApiVersionCheck { .. } => { Err(ServerError { message: "unexpected response".to_string(), io: None }) } } @@ -65,9 +99,10 @@ impl Process { fn run( path: AbsPathBuf, args: impl IntoIterator<Item = impl AsRef<OsStr>>, + null_stderr: bool, ) -> io::Result<Process> { let args: Vec<OsString> = args.into_iter().map(|s| s.as_ref().into()).collect(); - let child = JodChild(mk_child(&path, args)?); + let child = JodChild(mk_child(&path, args, null_stderr)?); Ok(Process { child }) } @@ -83,13 +118,14 @@ impl Process { fn mk_child( path: &AbsPath, args: impl IntoIterator<Item = impl AsRef<OsStr>>, + null_stderr: bool, ) -> io::Result<Child> { Command::new(path.as_os_str()) .args(args) .env("RUST_ANALYZER_INTERNALS_DO_NOT_USE", "this is unstable") .stdin(Stdio::piped()) .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) + .stderr(if null_stderr { Stdio::null() } else { Stdio::inherit() }) .spawn() } diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/version.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/version.rs index 40125c2a5..cf637ec35 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/version.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/version.rs @@ -120,17 +120,20 @@ pub fn read_version(dylib_path: &AbsPath) -> io::Result<String> { let version = u32::from_be_bytes([dot_rustc[4], dot_rustc[5], dot_rustc[6], dot_rustc[7]]); // Last supported version is: // https://github.com/rust-lang/rust/commit/0696e79f2740ad89309269b460579e548a5cd632 - match version { - 5 | 6 => {} + let snappy_portion = match version { + 5 | 6 => &dot_rustc[8..], + 7 => { + let len_bytes = &dot_rustc[8..12]; + let data_len = u32::from_be_bytes(len_bytes.try_into().unwrap()) as usize; + &dot_rustc[12..data_len + 12] + } _ => { return Err(io::Error::new( io::ErrorKind::InvalidData, format!("unsupported metadata version {version}"), )); } - } - - let snappy_portion = &dot_rustc[8..]; + }; let mut snappy_decoder = SnapDecoder::new(snappy_portion); diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml index 7991e125a..c402bc022 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml +++ b/src/tools/rust-analyzer/crates/proc-macro-srv-cli/Cargo.toml @@ -2,12 +2,14 @@ name = "proc-macro-srv-cli" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [dependencies] -proc-macro-srv = { version = "0.0.0", path = "../proc-macro-srv" } +proc-macro-srv.workspace = true [features] sysroot-abi = ["proc-macro-srv/sysroot-abi"] diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml b/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml index a136abc12..f7f07cfcb 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/Cargo.toml @@ -2,15 +2,17 @@ name = "proc-macro-srv" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false [dependencies] -object = { version = "0.29.0", default-features = false, features = [ +object = { version = "0.30.2", default-features = false, features = [ "std", "read_core", "elf", @@ -20,16 +22,16 @@ object = { version = "0.29.0", default-features = false, features = [ libloading = "0.7.3" memmap2 = "0.5.4" -tt = { path = "../tt", version = "0.0.0" } -mbe = { path = "../mbe", version = "0.0.0" } -paths = { path = "../paths", version = "0.0.0" } -proc-macro-api = { path = "../proc-macro-api", version = "0.0.0" } +tt.workspace = true +mbe.workspace = true +paths.workspace = true +proc-macro-api.workspace = true [dev-dependencies] expect-test = "1.4.0" # used as proc macro test targets -proc-macro-test = { path = "../proc-macro-test" } +proc-macro-test.workspace = true [features] sysroot-abi = [] diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/mod.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/mod.rs deleted file mode 100644 index 1c91ac0fa..000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/mod.rs +++ /dev/null @@ -1,104 +0,0 @@ -//! Macro ABI for version 1.58 of rustc - -#[allow(dead_code)] -#[doc(hidden)] -mod proc_macro; - -#[allow(dead_code)] -#[doc(hidden)] -mod ra_server; - -use libloading::Library; -use proc_macro_api::ProcMacroKind; - -use super::PanicMessage; - -pub(crate) struct Abi { - exported_macros: Vec<proc_macro::bridge::client::ProcMacro>, -} - -impl From<proc_macro::bridge::PanicMessage> for PanicMessage { - fn from(p: proc_macro::bridge::PanicMessage) -> Self { - Self { message: p.as_str().map(|s| s.to_string()) } - } -} - -impl Abi { - pub unsafe fn from_lib(lib: &Library, symbol_name: String) -> Result<Abi, libloading::Error> { - let macros: libloading::Symbol<'_, &&[proc_macro::bridge::client::ProcMacro]> = - lib.get(symbol_name.as_bytes())?; - Ok(Self { exported_macros: macros.to_vec() }) - } - - pub fn expand( - &self, - macro_name: &str, - macro_body: &tt::Subtree, - attributes: Option<&tt::Subtree>, - ) -> Result<tt::Subtree, PanicMessage> { - let parsed_body = ra_server::TokenStream::with_subtree(macro_body.clone()); - - let parsed_attributes = attributes.map_or(ra_server::TokenStream::new(), |attr| { - ra_server::TokenStream::with_subtree(attr.clone()) - }); - - for proc_macro in &self.exported_macros { - match proc_macro { - proc_macro::bridge::client::ProcMacro::CustomDerive { - trait_name, client, .. - } if *trait_name == macro_name => { - let res = client.run( - &proc_macro::bridge::server::SameThread, - ra_server::RustAnalyzer::default(), - parsed_body, - true, - ); - return res.map(|it| it.into_subtree()).map_err(PanicMessage::from); - } - proc_macro::bridge::client::ProcMacro::Bang { name, client } - if *name == macro_name => - { - let res = client.run( - &proc_macro::bridge::server::SameThread, - ra_server::RustAnalyzer::default(), - parsed_body, - true, - ); - return res.map(|it| it.into_subtree()).map_err(PanicMessage::from); - } - proc_macro::bridge::client::ProcMacro::Attr { name, client } - if *name == macro_name => - { - let res = client.run( - &proc_macro::bridge::server::SameThread, - ra_server::RustAnalyzer::default(), - parsed_attributes, - parsed_body, - true, - ); - return res.map(|it| it.into_subtree()).map_err(PanicMessage::from); - } - _ => continue, - } - } - - Err(proc_macro::bridge::PanicMessage::String("Nothing to expand".to_string()).into()) - } - - pub fn list_macros(&self) -> Vec<(String, ProcMacroKind)> { - self.exported_macros - .iter() - .map(|proc_macro| match proc_macro { - proc_macro::bridge::client::ProcMacro::CustomDerive { trait_name, .. } => { - (trait_name.to_string(), ProcMacroKind::CustomDerive) - } - proc_macro::bridge::client::ProcMacro::Bang { name, .. } => { - (name.to_string(), ProcMacroKind::FuncLike) - } - proc_macro::bridge::client::ProcMacro::Attr { name, .. } => { - (name.to_string(), ProcMacroKind::Attr) - } - }) - .collect() - } -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/buffer.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/buffer.rs deleted file mode 100644 index d82669d3e..000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/buffer.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! Buffer management for same-process client<->server communication. - -use std::io::{self, Write}; -use std::mem; -use std::ops::{Deref, DerefMut}; -use std::slice; - -#[repr(C)] -pub struct Buffer<T: Copy> { - data: *mut T, - len: usize, - capacity: usize, - reserve: extern "C" fn(Buffer<T>, usize) -> Buffer<T>, - drop: extern "C" fn(Buffer<T>), -} - -unsafe impl<T: Copy + Sync> Sync for Buffer<T> {} -unsafe impl<T: Copy + Send> Send for Buffer<T> {} - -impl<T: Copy> Default for Buffer<T> { - fn default() -> Self { - Self::from(vec![]) - } -} - -impl<T: Copy> Deref for Buffer<T> { - type Target = [T]; - fn deref(&self) -> &[T] { - unsafe { slice::from_raw_parts(self.data as *const T, self.len) } - } -} - -impl<T: Copy> DerefMut for Buffer<T> { - fn deref_mut(&mut self) -> &mut [T] { - unsafe { slice::from_raw_parts_mut(self.data, self.len) } - } -} - -impl<T: Copy> Buffer<T> { - pub(super) fn new() -> Self { - Self::default() - } - - pub(super) fn clear(&mut self) { - self.len = 0; - } - - pub(super) fn take(&mut self) -> Self { - mem::take(self) - } - - // We have the array method separate from extending from a slice. This is - // because in the case of small arrays, codegen can be more efficient - // (avoiding a memmove call). With extend_from_slice, LLVM at least - // currently is not able to make that optimization. - pub(super) fn extend_from_array<const N: usize>(&mut self, xs: &[T; N]) { - if xs.len() > (self.capacity - self.len) { - let b = self.take(); - *self = (b.reserve)(b, xs.len()); - } - unsafe { - xs.as_ptr().copy_to_nonoverlapping(self.data.add(self.len), xs.len()); - self.len += xs.len(); - } - } - - pub(super) fn extend_from_slice(&mut self, xs: &[T]) { - if xs.len() > (self.capacity - self.len) { - let b = self.take(); - *self = (b.reserve)(b, xs.len()); - } - unsafe { - xs.as_ptr().copy_to_nonoverlapping(self.data.add(self.len), xs.len()); - self.len += xs.len(); - } - } - - pub(super) fn push(&mut self, v: T) { - // The code here is taken from Vec::push, and we know that reserve() - // will panic if we're exceeding isize::MAX bytes and so there's no need - // to check for overflow. - if self.len == self.capacity { - let b = self.take(); - *self = (b.reserve)(b, 1); - } - unsafe { - *self.data.add(self.len) = v; - self.len += 1; - } - } -} - -impl Write for Buffer<u8> { - fn write(&mut self, xs: &[u8]) -> io::Result<usize> { - self.extend_from_slice(xs); - Ok(xs.len()) - } - - fn write_all(&mut self, xs: &[u8]) -> io::Result<()> { - self.extend_from_slice(xs); - Ok(()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl<T: Copy> Drop for Buffer<T> { - fn drop(&mut self) { - let b = self.take(); - (b.drop)(b); - } -} - -impl<T: Copy> From<Vec<T>> for Buffer<T> { - fn from(mut v: Vec<T>) -> Self { - let (data, len, capacity) = (v.as_mut_ptr(), v.len(), v.capacity()); - mem::forget(v); - - // This utility function is nested in here because it can *only* - // be safely called on `Buffer`s created by *this* `proc_macro`. - fn to_vec<T: Copy>(b: Buffer<T>) -> Vec<T> { - unsafe { - let Buffer { data, len, capacity, .. } = b; - mem::forget(b); - Vec::from_raw_parts(data, len, capacity) - } - } - - extern "C" fn reserve<T: Copy>(b: Buffer<T>, additional: usize) -> Buffer<T> { - let mut v = to_vec(b); - v.reserve(additional); - Buffer::from(v) - } - - extern "C" fn drop<T: Copy>(b: Buffer<T>) { - mem::drop(to_vec(b)); - } - - Buffer { data, len, capacity, reserve, drop } - } -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/client.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/client.rs deleted file mode 100644 index e78842f5c..000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/client.rs +++ /dev/null @@ -1,485 +0,0 @@ -//! Client-side types. - -use super::*; - -macro_rules! define_handles { - ( - 'owned: $($oty:ident,)* - 'interned: $($ity:ident,)* - ) => { - #[repr(C)] - #[allow(non_snake_case)] - pub struct HandleCounters { - $($oty: AtomicUsize,)* - $($ity: AtomicUsize,)* - } - - impl HandleCounters { - // FIXME(eddyb) use a reference to the `static COUNTERS`, instead of - // a wrapper `fn` pointer, once `const fn` can reference `static`s. - extern "C" fn get() -> &'static Self { - static COUNTERS: HandleCounters = HandleCounters { - $($oty: AtomicUsize::new(1),)* - $($ity: AtomicUsize::new(1),)* - }; - &COUNTERS - } - } - - // FIXME(eddyb) generate the definition of `HandleStore` in `server.rs`. - #[repr(C)] - #[allow(non_snake_case)] - pub(super) struct HandleStore<S: server::Types> { - $($oty: handle::OwnedStore<S::$oty>,)* - $($ity: handle::InternedStore<S::$ity>,)* - } - - impl<S: server::Types> HandleStore<S> { - pub(super) fn new(handle_counters: &'static HandleCounters) -> Self { - HandleStore { - $($oty: handle::OwnedStore::new(&handle_counters.$oty),)* - $($ity: handle::InternedStore::new(&handle_counters.$ity),)* - } - } - } - - $( - #[repr(C)] - pub(crate) struct $oty(handle::Handle); - - // Forward `Drop::drop` to the inherent `drop` method. - impl Drop for $oty { - fn drop(&mut self) { - $oty(self.0).drop(); - } - } - - impl<S> Encode<S> for $oty { - fn encode(self, w: &mut Writer, s: &mut S) { - let handle = self.0; - mem::forget(self); - handle.encode(w, s); - } - } - - impl<S: server::Types> DecodeMut<'_, '_, HandleStore<server::MarkedTypes<S>>> - for Marked<S::$oty, $oty> - { - fn decode(r: &mut Reader<'_>, s: &mut HandleStore<server::MarkedTypes<S>>) -> Self { - s.$oty.take(handle::Handle::decode(r, &mut ())) - } - } - - impl<S> Encode<S> for &$oty { - fn encode(self, w: &mut Writer, s: &mut S) { - self.0.encode(w, s); - } - } - - impl<'s, S: server::Types> Decode<'_, 's, HandleStore<server::MarkedTypes<S>>> - for &'s Marked<S::$oty, $oty> - { - fn decode(r: &mut Reader<'_>, s: &'s HandleStore<server::MarkedTypes<S>>) -> Self { - &s.$oty[handle::Handle::decode(r, &mut ())] - } - } - - impl<S> Encode<S> for &mut $oty { - fn encode(self, w: &mut Writer, s: &mut S) { - self.0.encode(w, s); - } - } - - impl<'s, S: server::Types> DecodeMut<'_, 's, HandleStore<server::MarkedTypes<S>>> - for &'s mut Marked<S::$oty, $oty> - { - fn decode( - r: &mut Reader<'_>, - s: &'s mut HandleStore<server::MarkedTypes<S>> - ) -> Self { - &mut s.$oty[handle::Handle::decode(r, &mut ())] - } - } - - impl<S: server::Types> Encode<HandleStore<server::MarkedTypes<S>>> - for Marked<S::$oty, $oty> - { - fn encode(self, w: &mut Writer, s: &mut HandleStore<server::MarkedTypes<S>>) { - s.$oty.alloc(self).encode(w, s); - } - } - - impl<S> DecodeMut<'_, '_, S> for $oty { - fn decode(r: &mut Reader<'_>, s: &mut S) -> Self { - $oty(handle::Handle::decode(r, s)) - } - } - )* - - $( - #[repr(C)] - #[derive(Copy, Clone, PartialEq, Eq, Hash)] - pub(crate) struct $ity(handle::Handle); - - impl<S> Encode<S> for $ity { - fn encode(self, w: &mut Writer, s: &mut S) { - self.0.encode(w, s); - } - } - - impl<S: server::Types> DecodeMut<'_, '_, HandleStore<server::MarkedTypes<S>>> - for Marked<S::$ity, $ity> - { - fn decode(r: &mut Reader<'_>, s: &mut HandleStore<server::MarkedTypes<S>>) -> Self { - s.$ity.copy(handle::Handle::decode(r, &mut ())) - } - } - - impl<S: server::Types> Encode<HandleStore<server::MarkedTypes<S>>> - for Marked<S::$ity, $ity> - { - fn encode(self, w: &mut Writer, s: &mut HandleStore<server::MarkedTypes<S>>) { - s.$ity.alloc(self).encode(w, s); - } - } - - impl<S> DecodeMut<'_, '_, S> for $ity { - fn decode(r: &mut Reader<'_>, s: &mut S) -> Self { - $ity(handle::Handle::decode(r, s)) - } - } - )* - } -} -define_handles! { - 'owned: - FreeFunctions, - TokenStream, - TokenStreamBuilder, - TokenStreamIter, - Group, - Literal, - SourceFile, - MultiSpan, - Diagnostic, - - 'interned: - Punct, - Ident, - Span, -} - -// FIXME(eddyb) generate these impls by pattern-matching on the -// names of methods - also could use the presence of `fn drop` -// to distinguish between 'owned and 'interned, above. -// Alternatively, special 'modes" could be listed of types in with_api -// instead of pattern matching on methods, here and in server decl. - -impl Clone for TokenStream { - fn clone(&self) -> Self { - self.clone() - } -} - -impl Clone for TokenStreamIter { - fn clone(&self) -> Self { - self.clone() - } -} - -impl Clone for Group { - fn clone(&self) -> Self { - self.clone() - } -} - -impl Clone for Literal { - fn clone(&self) -> Self { - self.clone() - } -} - -impl fmt::Debug for Literal { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Literal") - // format the kind without quotes, as in `kind: Float` - .field("kind", &format_args!("{}", &self.debug_kind())) - .field("symbol", &self.symbol()) - // format `Some("...")` on one line even in {:#?} mode - .field("suffix", &format_args!("{:?}", &self.suffix())) - .field("span", &self.span()) - .finish() - } -} - -impl Clone for SourceFile { - fn clone(&self) -> Self { - self.clone() - } -} - -impl fmt::Debug for Span { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.debug()) - } -} - -macro_rules! define_client_side { - ($($name:ident { - $(fn $method:ident($($arg:ident: $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)*;)* - }),* $(,)?) => { - $(impl $name { - $(pub(crate) fn $method($($arg: $arg_ty),*) $(-> $ret_ty)* { - Bridge::with(|bridge| { - let mut b = bridge.cached_buffer.take(); - - b.clear(); - api_tags::Method::$name(api_tags::$name::$method).encode(&mut b, &mut ()); - reverse_encode!(b; $($arg),*); - - b = bridge.dispatch.call(b); - - let r = Result::<_, PanicMessage>::decode(&mut &b[..], &mut ()); - - bridge.cached_buffer = b; - - r.unwrap_or_else(|e| panic::resume_unwind(e.into())) - }) - })* - })* - } -} -with_api!(self, self, define_client_side); - -enum BridgeState<'a> { - /// No server is currently connected to this client. - NotConnected, - - /// A server is connected and available for requests. - Connected(Bridge<'a>), - - /// Access to the bridge is being exclusively acquired - /// (e.g., during `BridgeState::with`). - InUse, -} - -enum BridgeStateL {} - -impl<'a> scoped_cell::ApplyL<'a> for BridgeStateL { - type Out = BridgeState<'a>; -} - -thread_local! { - static BRIDGE_STATE: scoped_cell::ScopedCell<BridgeStateL> = - scoped_cell::ScopedCell::new(BridgeState::NotConnected); -} - -impl BridgeState<'_> { - /// Take exclusive control of the thread-local - /// `BridgeState`, and pass it to `f`, mutably. - /// The state will be restored after `f` exits, even - /// by panic, including modifications made to it by `f`. - /// - /// N.B., while `f` is running, the thread-local state - /// is `BridgeState::InUse`. - fn with<R>(f: impl FnOnce(&mut BridgeState<'_>) -> R) -> R { - BRIDGE_STATE.with(|state| { - state.replace(BridgeState::InUse, |mut state| { - // FIXME(#52812) pass `f` directly to `replace` when `RefMutL` is gone - f(&mut state) - }) - }) - } -} - -impl Bridge<'_> { - pub(crate) fn is_available() -> bool { - BridgeState::with(|state| match state { - BridgeState::Connected(_) | BridgeState::InUse => true, - BridgeState::NotConnected => false, - }) - } - - fn enter<R>(self, f: impl FnOnce() -> R) -> R { - let force_show_panics = self.force_show_panics; - // Hide the default panic output within `proc_macro` expansions. - // NB. the server can't do this because it may use a different libstd. - static HIDE_PANICS_DURING_EXPANSION: Once = Once::new(); - HIDE_PANICS_DURING_EXPANSION.call_once(|| { - let prev = panic::take_hook(); - panic::set_hook(Box::new(move |info| { - let show = BridgeState::with(|state| match state { - BridgeState::NotConnected => true, - BridgeState::Connected(_) | BridgeState::InUse => force_show_panics, - }); - if show { - prev(info) - } - })); - }); - - BRIDGE_STATE.with(|state| state.set(BridgeState::Connected(self), f)) - } - - fn with<R>(f: impl FnOnce(&mut Bridge<'_>) -> R) -> R { - BridgeState::with(|state| match state { - BridgeState::NotConnected => { - panic!("procedural macro API is used outside of a procedural macro"); - } - BridgeState::InUse => { - panic!("procedural macro API is used while it's already in use"); - } - BridgeState::Connected(bridge) => f(bridge), - }) - } -} - -/// A client-side "global object" (usually a function pointer), -/// which may be using a different `proc_macro` from the one -/// used by the server, but can be interacted with compatibly. -/// -/// N.B., `F` must have FFI-friendly memory layout (e.g., a pointer). -/// The call ABI of function pointers used for `F` doesn't -/// need to match between server and client, since it's only -/// passed between them and (eventually) called by the client. -#[repr(C)] -#[derive(Copy, Clone)] -pub struct Client<F> { - // FIXME(eddyb) use a reference to the `static COUNTERS`, instead of - // a wrapper `fn` pointer, once `const fn` can reference `static`s. - pub(super) get_handle_counters: extern "C" fn() -> &'static HandleCounters, - pub(super) run: extern "C" fn(Bridge<'_>, F) -> Buffer<u8>, - pub(super) f: F, -} - -/// Client-side helper for handling client panics, entering the bridge, -/// deserializing input and serializing output. -// FIXME(eddyb) maybe replace `Bridge::enter` with this? -fn run_client<A: for<'a, 's> DecodeMut<'a, 's, ()>, R: Encode<()>>( - mut bridge: Bridge<'_>, - f: impl FnOnce(A) -> R, -) -> Buffer<u8> { - // The initial `cached_buffer` contains the input. - let mut b = bridge.cached_buffer.take(); - - panic::catch_unwind(panic::AssertUnwindSafe(|| { - bridge.enter(|| { - let reader = &mut &b[..]; - let input = A::decode(reader, &mut ()); - - // Put the `cached_buffer` back in the `Bridge`, for requests. - Bridge::with(|bridge| bridge.cached_buffer = b.take()); - - let output = f(input); - - // Take the `cached_buffer` back out, for the output value. - b = Bridge::with(|bridge| bridge.cached_buffer.take()); - - // HACK(eddyb) Separate encoding a success value (`Ok(output)`) - // from encoding a panic (`Err(e: PanicMessage)`) to avoid - // having handles outside the `bridge.enter(|| ...)` scope, and - // to catch panics that could happen while encoding the success. - // - // Note that panics should be impossible beyond this point, but - // this is defensively trying to avoid any accidental panicking - // reaching the `extern "C"` (which should `abort` but might not - // at the moment, so this is also potentially preventing UB). - b.clear(); - Ok::<_, ()>(output).encode(&mut b, &mut ()); - }) - })) - .map_err(PanicMessage::from) - .unwrap_or_else(|e| { - b.clear(); - Err::<(), _>(e).encode(&mut b, &mut ()); - }); - b -} - -impl Client<fn(super::super::TokenStream) -> super::super::TokenStream> { - pub fn expand1(f: fn(super::super::TokenStream) -> super::super::TokenStream) -> Self { - extern "C" fn run( - bridge: Bridge<'_>, - f: impl FnOnce(super::super::TokenStream) -> super::super::TokenStream, - ) -> Buffer<u8> { - run_client(bridge, |input| f(super::super::TokenStream(input)).0) - } - Client { get_handle_counters: HandleCounters::get, run, f } - } -} - -impl Client<fn(super::super::TokenStream, super::super::TokenStream) -> super::super::TokenStream> { - pub fn expand2( - f: fn(super::super::TokenStream, super::super::TokenStream) -> super::super::TokenStream, - ) -> Self { - extern "C" fn run( - bridge: Bridge<'_>, - f: impl FnOnce( - super::super::TokenStream, - super::super::TokenStream, - ) -> super::super::TokenStream, - ) -> Buffer<u8> { - run_client(bridge, |(input, input2)| { - f(super::super::TokenStream(input), super::super::TokenStream(input2)).0 - }) - } - Client { get_handle_counters: HandleCounters::get, run, f } - } -} - -#[repr(C)] -#[derive(Copy, Clone)] -pub enum ProcMacro { - CustomDerive { - trait_name: &'static str, - attributes: &'static [&'static str], - client: Client<fn(super::super::TokenStream) -> super::super::TokenStream>, - }, - - Attr { - name: &'static str, - client: Client< - fn(super::super::TokenStream, super::super::TokenStream) -> super::super::TokenStream, - >, - }, - - Bang { - name: &'static str, - client: Client<fn(super::super::TokenStream) -> super::super::TokenStream>, - }, -} - -impl ProcMacro { - pub fn name(&self) -> &'static str { - match self { - ProcMacro::CustomDerive { trait_name, .. } => trait_name, - ProcMacro::Attr { name, .. } => name, - ProcMacro::Bang { name, .. } => name, - } - } - - pub fn custom_derive( - trait_name: &'static str, - attributes: &'static [&'static str], - expand: fn(super::super::TokenStream) -> super::super::TokenStream, - ) -> Self { - ProcMacro::CustomDerive { trait_name, attributes, client: Client::expand1(expand) } - } - - pub fn attr( - name: &'static str, - expand: fn( - super::super::TokenStream, - super::super::TokenStream, - ) -> super::super::TokenStream, - ) -> Self { - ProcMacro::Attr { name, client: Client::expand2(expand) } - } - - pub fn bang( - name: &'static str, - expand: fn(super::super::TokenStream) -> super::super::TokenStream, - ) -> Self { - ProcMacro::Bang { name, client: Client::expand1(expand) } - } -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/closure.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/closure.rs deleted file mode 100644 index 5be71cc3d..000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/closure.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Closure type (equivalent to `&mut dyn FnMut(A) -> R`) that's `repr(C)`. - -#[repr(C)] -pub struct Closure<'a, A, R> { - call: unsafe extern "C" fn(&mut Env, A) -> R, - env: &'a mut Env, -} - -struct Env; - -impl<'a, A, R, F: FnMut(A) -> R> From<&'a mut F> for Closure<'a, A, R> { - fn from(f: &'a mut F) -> Self { - unsafe extern "C" fn call<A, R, F: FnMut(A) -> R>(env: &mut Env, arg: A) -> R { - (*(env as *mut _ as *mut F))(arg) - } - Closure { call: call::<A, R, F>, env: unsafe { &mut *(f as *mut _ as *mut Env) } } - } -} - -impl<'a, A, R> Closure<'a, A, R> { - pub fn call(&mut self, arg: A) -> R { - unsafe { (self.call)(self.env, arg) } - } -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/handle.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/handle.rs deleted file mode 100644 index bcbb86812..000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/handle.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Server-side handles and storage for per-handle data. - -use std::collections::{BTreeMap, HashMap}; -use std::hash::Hash; -use std::num::NonZeroU32; -use std::ops::{Index, IndexMut}; -use std::sync::atomic::{AtomicUsize, Ordering}; - -pub(super) type Handle = NonZeroU32; - -pub(super) struct OwnedStore<T: 'static> { - counter: &'static AtomicUsize, - data: BTreeMap<Handle, T>, -} - -impl<T> OwnedStore<T> { - pub(super) fn new(counter: &'static AtomicUsize) -> Self { - // Ensure the handle counter isn't 0, which would panic later, - // when `NonZeroU32::new` (aka `Handle::new`) is called in `alloc`. - assert_ne!(counter.load(Ordering::SeqCst), 0); - - OwnedStore { counter, data: BTreeMap::new() } - } -} - -impl<T> OwnedStore<T> { - pub(super) fn alloc(&mut self, x: T) -> Handle { - let counter = self.counter.fetch_add(1, Ordering::SeqCst); - let handle = Handle::new(counter as u32).expect("`proc_macro` handle counter overflowed"); - assert!(self.data.insert(handle, x).is_none()); - handle - } - - pub(super) fn take(&mut self, h: Handle) -> T { - self.data.remove(&h).expect("use-after-free in `proc_macro` handle") - } -} - -impl<T> Index<Handle> for OwnedStore<T> { - type Output = T; - fn index(&self, h: Handle) -> &T { - self.data.get(&h).expect("use-after-free in `proc_macro` handle") - } -} - -impl<T> IndexMut<Handle> for OwnedStore<T> { - fn index_mut(&mut self, h: Handle) -> &mut T { - self.data.get_mut(&h).expect("use-after-free in `proc_macro` handle") - } -} - -pub(super) struct InternedStore<T: 'static> { - owned: OwnedStore<T>, - interner: HashMap<T, Handle>, -} - -impl<T: Copy + Eq + Hash> InternedStore<T> { - pub(super) fn new(counter: &'static AtomicUsize) -> Self { - InternedStore { owned: OwnedStore::new(counter), interner: HashMap::new() } - } - - pub(super) fn alloc(&mut self, x: T) -> Handle { - let owned = &mut self.owned; - *self.interner.entry(x).or_insert_with(|| owned.alloc(x)) - } - - pub(super) fn copy(&mut self, h: Handle) -> T { - self.owned[h] - } -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/mod.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/mod.rs deleted file mode 100644 index b7968c529..000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/mod.rs +++ /dev/null @@ -1,429 +0,0 @@ -//! Internal interface for communicating between a `proc_macro` client -//! (a proc macro crate) and a `proc_macro` server (a compiler front-end). -//! -//! Serialization (with C ABI buffers) and unique integer handles are employed -//! to allow safely interfacing between two copies of `proc_macro` built -//! (from the same source) by different compilers with potentially mismatching -//! Rust ABIs (e.g., stage0/bin/rustc vs stage1/bin/rustc during bootstrap). - -#![deny(unsafe_code)] - -pub use super::{Delimiter, Level, LineColumn, Spacing}; -use std::fmt; -use std::hash::Hash; -use std::marker; -use std::mem; -use std::ops::Bound; -use std::panic; -use std::sync::atomic::AtomicUsize; -use std::sync::Once; -use std::thread; - -/// Higher-order macro describing the server RPC API, allowing automatic -/// generation of type-safe Rust APIs, both client-side and server-side. -/// -/// `with_api!(MySelf, my_self, my_macro)` expands to: -/// ```rust,ignore (pseudo-code) -/// my_macro! { -/// // ... -/// Literal { -/// // ... -/// fn character(ch: char) -> MySelf::Literal; -/// // ... -/// fn span(my_self: &MySelf::Literal) -> MySelf::Span; -/// fn set_span(my_self: &mut MySelf::Literal, span: MySelf::Span); -/// }, -/// // ... -/// } -/// ``` -/// -/// The first two arguments serve to customize the arguments names -/// and argument/return types, to enable several different usecases: -/// -/// If `my_self` is just `self`, then each `fn` signature can be used -/// as-is for a method. If it's anything else (`self_` in practice), -/// then the signatures don't have a special `self` argument, and -/// can, therefore, have a different one introduced. -/// -/// If `MySelf` is just `Self`, then the types are only valid inside -/// a trait or a trait impl, where the trait has associated types -/// for each of the API types. If non-associated types are desired, -/// a module name (`self` in practice) can be used instead of `Self`. -macro_rules! with_api { - ($S:ident, $self:ident, $m:ident) => { - $m! { - FreeFunctions { - fn drop($self: $S::FreeFunctions); - fn track_env_var(var: &str, value: Option<&str>); - fn track_path(path: &str); - }, - TokenStream { - fn drop($self: $S::TokenStream); - fn clone($self: &$S::TokenStream) -> $S::TokenStream; - fn new() -> $S::TokenStream; - fn is_empty($self: &$S::TokenStream) -> bool; - fn expand_expr($self: &$S::TokenStream) -> Result<$S::TokenStream, ()>; - fn from_str(src: &str) -> $S::TokenStream; - fn to_string($self: &$S::TokenStream) -> String; - fn from_token_tree( - tree: TokenTree<$S::Group, $S::Punct, $S::Ident, $S::Literal>, - ) -> $S::TokenStream; - fn into_iter($self: $S::TokenStream) -> $S::TokenStreamIter; - }, - TokenStreamBuilder { - fn drop($self: $S::TokenStreamBuilder); - fn new() -> $S::TokenStreamBuilder; - fn push($self: &mut $S::TokenStreamBuilder, stream: $S::TokenStream); - fn build($self: $S::TokenStreamBuilder) -> $S::TokenStream; - }, - TokenStreamIter { - fn drop($self: $S::TokenStreamIter); - fn clone($self: &$S::TokenStreamIter) -> $S::TokenStreamIter; - fn next( - $self: &mut $S::TokenStreamIter, - ) -> Option<TokenTree<$S::Group, $S::Punct, $S::Ident, $S::Literal>>; - }, - Group { - fn drop($self: $S::Group); - fn clone($self: &$S::Group) -> $S::Group; - fn new(delimiter: Delimiter, stream: $S::TokenStream) -> $S::Group; - fn delimiter($self: &$S::Group) -> Delimiter; - fn stream($self: &$S::Group) -> $S::TokenStream; - fn span($self: &$S::Group) -> $S::Span; - fn span_open($self: &$S::Group) -> $S::Span; - fn span_close($self: &$S::Group) -> $S::Span; - fn set_span($self: &mut $S::Group, span: $S::Span); - }, - Punct { - fn new(ch: char, spacing: Spacing) -> $S::Punct; - fn as_char($self: $S::Punct) -> char; - fn spacing($self: $S::Punct) -> Spacing; - fn span($self: $S::Punct) -> $S::Span; - fn with_span($self: $S::Punct, span: $S::Span) -> $S::Punct; - }, - Ident { - fn new(string: &str, span: $S::Span, is_raw: bool) -> $S::Ident; - fn span($self: $S::Ident) -> $S::Span; - fn with_span($self: $S::Ident, span: $S::Span) -> $S::Ident; - }, - Literal { - fn drop($self: $S::Literal); - fn clone($self: &$S::Literal) -> $S::Literal; - fn from_str(s: &str) -> Result<$S::Literal, ()>; - fn to_string($self: &$S::Literal) -> String; - fn debug_kind($self: &$S::Literal) -> String; - fn symbol($self: &$S::Literal) -> String; - fn suffix($self: &$S::Literal) -> Option<String>; - fn integer(n: &str) -> $S::Literal; - fn typed_integer(n: &str, kind: &str) -> $S::Literal; - fn float(n: &str) -> $S::Literal; - fn f32(n: &str) -> $S::Literal; - fn f64(n: &str) -> $S::Literal; - fn string(string: &str) -> $S::Literal; - fn character(ch: char) -> $S::Literal; - fn byte_string(bytes: &[u8]) -> $S::Literal; - fn span($self: &$S::Literal) -> $S::Span; - fn set_span($self: &mut $S::Literal, span: $S::Span); - fn subspan( - $self: &$S::Literal, - start: Bound<usize>, - end: Bound<usize>, - ) -> Option<$S::Span>; - }, - SourceFile { - fn drop($self: $S::SourceFile); - fn clone($self: &$S::SourceFile) -> $S::SourceFile; - fn eq($self: &$S::SourceFile, other: &$S::SourceFile) -> bool; - fn path($self: &$S::SourceFile) -> String; - fn is_real($self: &$S::SourceFile) -> bool; - }, - MultiSpan { - fn drop($self: $S::MultiSpan); - fn new() -> $S::MultiSpan; - fn push($self: &mut $S::MultiSpan, span: $S::Span); - }, - Diagnostic { - fn drop($self: $S::Diagnostic); - fn new(level: Level, msg: &str, span: $S::MultiSpan) -> $S::Diagnostic; - fn sub( - $self: &mut $S::Diagnostic, - level: Level, - msg: &str, - span: $S::MultiSpan, - ); - fn emit($self: $S::Diagnostic); - }, - Span { - fn debug($self: $S::Span) -> String; - fn def_site() -> $S::Span; - fn call_site() -> $S::Span; - fn mixed_site() -> $S::Span; - fn source_file($self: $S::Span) -> $S::SourceFile; - fn parent($self: $S::Span) -> Option<$S::Span>; - fn source($self: $S::Span) -> $S::Span; - fn start($self: $S::Span) -> LineColumn; - fn end($self: $S::Span) -> LineColumn; - fn before($self: $S::Span) -> $S::Span; - fn after($self: $S::Span) -> $S::Span; - fn join($self: $S::Span, other: $S::Span) -> Option<$S::Span>; - fn resolved_at($self: $S::Span, at: $S::Span) -> $S::Span; - fn source_text($self: $S::Span) -> Option<String>; - fn save_span($self: $S::Span) -> usize; - fn recover_proc_macro_span(id: usize) -> $S::Span; - }, - } - }; -} - -// FIXME(eddyb) this calls `encode` for each argument, but in reverse, -// to avoid borrow conflicts from borrows started by `&mut` arguments. -macro_rules! reverse_encode { - ($writer:ident;) => {}; - ($writer:ident; $first:ident $(, $rest:ident)*) => { - reverse_encode!($writer; $($rest),*); - $first.encode(&mut $writer, &mut ()); - } -} - -// FIXME(eddyb) this calls `decode` for each argument, but in reverse, -// to avoid borrow conflicts from borrows started by `&mut` arguments. -macro_rules! reverse_decode { - ($reader:ident, $s:ident;) => {}; - ($reader:ident, $s:ident; $first:ident: $first_ty:ty $(, $rest:ident: $rest_ty:ty)*) => { - reverse_decode!($reader, $s; $($rest: $rest_ty),*); - let $first = <$first_ty>::decode(&mut $reader, $s); - } -} - -#[allow(unsafe_code)] -mod buffer; -#[forbid(unsafe_code)] -pub mod client; -#[allow(unsafe_code)] -mod closure; -#[forbid(unsafe_code)] -mod handle; -#[macro_use] -#[forbid(unsafe_code)] -mod rpc; -#[allow(unsafe_code)] -mod scoped_cell; -#[forbid(unsafe_code)] -pub mod server; - -use buffer::Buffer; -pub use rpc::PanicMessage; -use rpc::{Decode, DecodeMut, Encode, Reader, Writer}; - -/// An active connection between a server and a client. -/// The server creates the bridge (`Bridge::run_server` in `server.rs`), -/// then passes it to the client through the function pointer in the `run` -/// field of `client::Client`. The client holds its copy of the `Bridge` -/// in TLS during its execution (`Bridge::{enter, with}` in `client.rs`). -#[repr(C)] -pub struct Bridge<'a> { - /// Reusable buffer (only `clear`-ed, never shrunk), primarily - /// used for making requests, but also for passing input to client. - cached_buffer: Buffer<u8>, - - /// Server-side function that the client uses to make requests. - dispatch: closure::Closure<'a, Buffer<u8>, Buffer<u8>>, - - /// If 'true', always invoke the default panic hook - force_show_panics: bool, -} - -#[forbid(unsafe_code)] -#[allow(non_camel_case_types)] -mod api_tags { - use super::rpc::{DecodeMut, Encode, Reader, Writer}; - - macro_rules! declare_tags { - ($($name:ident { - $(fn $method:ident($($arg:ident: $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)*;)* - }),* $(,)?) => { - $( - pub(super) enum $name { - $($method),* - } - rpc_encode_decode!(enum $name { $($method),* }); - )* - - - pub(super) enum Method { - $($name($name)),* - } - rpc_encode_decode!(enum Method { $($name(m)),* }); - } - } - with_api!(self, self, declare_tags); -} - -/// Helper to wrap associated types to allow trait impl dispatch. -/// That is, normally a pair of impls for `T::Foo` and `T::Bar` -/// can overlap, but if the impls are, instead, on types like -/// `Marked<T::Foo, Foo>` and `Marked<T::Bar, Bar>`, they can't. -trait Mark { - type Unmarked; - fn mark(unmarked: Self::Unmarked) -> Self; -} - -/// Unwrap types wrapped by `Mark::mark` (see `Mark` for details). -trait Unmark { - type Unmarked; - fn unmark(self) -> Self::Unmarked; -} - -#[derive(Copy, Clone, PartialEq, Eq, Hash)] -struct Marked<T, M> { - value: T, - _marker: marker::PhantomData<M>, -} - -impl<T, M> Mark for Marked<T, M> { - type Unmarked = T; - fn mark(unmarked: Self::Unmarked) -> Self { - Marked { value: unmarked, _marker: marker::PhantomData } - } -} -impl<T, M> Unmark for Marked<T, M> { - type Unmarked = T; - fn unmark(self) -> Self::Unmarked { - self.value - } -} -impl<'a, T, M> Unmark for &'a Marked<T, M> { - type Unmarked = &'a T; - fn unmark(self) -> Self::Unmarked { - &self.value - } -} -impl<'a, T, M> Unmark for &'a mut Marked<T, M> { - type Unmarked = &'a mut T; - fn unmark(self) -> Self::Unmarked { - &mut self.value - } -} - -impl<T: Mark> Mark for Option<T> { - type Unmarked = Option<T::Unmarked>; - fn mark(unmarked: Self::Unmarked) -> Self { - unmarked.map(T::mark) - } -} -impl<T: Unmark> Unmark for Option<T> { - type Unmarked = Option<T::Unmarked>; - fn unmark(self) -> Self::Unmarked { - self.map(T::unmark) - } -} - -impl<T: Mark, E: Mark> Mark for Result<T, E> { - type Unmarked = Result<T::Unmarked, E::Unmarked>; - fn mark(unmarked: Self::Unmarked) -> Self { - unmarked.map(T::mark).map_err(E::mark) - } -} -impl<T: Unmark, E: Unmark> Unmark for Result<T, E> { - type Unmarked = Result<T::Unmarked, E::Unmarked>; - fn unmark(self) -> Self::Unmarked { - self.map(T::unmark).map_err(E::unmark) - } -} - -macro_rules! mark_noop { - ($($ty:ty),* $(,)?) => { - $( - impl Mark for $ty { - type Unmarked = Self; - fn mark(unmarked: Self::Unmarked) -> Self { - unmarked - } - } - impl Unmark for $ty { - type Unmarked = Self; - fn unmark(self) -> Self::Unmarked { - self - } - } - )* - } -} -mark_noop! { - (), - bool, - char, - &'_ [u8], - &'_ str, - String, - usize, - Delimiter, - Level, - LineColumn, - Spacing, - Bound<usize>, -} - -rpc_encode_decode!( - enum Delimiter { - Parenthesis, - Brace, - Bracket, - None, - } -); -rpc_encode_decode!( - enum Level { - Error, - Warning, - Note, - Help, - } -); -rpc_encode_decode!(struct LineColumn { line, column }); -rpc_encode_decode!( - enum Spacing { - Alone, - Joint, - } -); - -#[derive(Clone)] -pub enum TokenTree<G, P, I, L> { - Group(G), - Punct(P), - Ident(I), - Literal(L), -} - -impl<G: Mark, P: Mark, I: Mark, L: Mark> Mark for TokenTree<G, P, I, L> { - type Unmarked = TokenTree<G::Unmarked, P::Unmarked, I::Unmarked, L::Unmarked>; - fn mark(unmarked: Self::Unmarked) -> Self { - match unmarked { - TokenTree::Group(tt) => TokenTree::Group(G::mark(tt)), - TokenTree::Punct(tt) => TokenTree::Punct(P::mark(tt)), - TokenTree::Ident(tt) => TokenTree::Ident(I::mark(tt)), - TokenTree::Literal(tt) => TokenTree::Literal(L::mark(tt)), - } - } -} -impl<G: Unmark, P: Unmark, I: Unmark, L: Unmark> Unmark for TokenTree<G, P, I, L> { - type Unmarked = TokenTree<G::Unmarked, P::Unmarked, I::Unmarked, L::Unmarked>; - fn unmark(self) -> Self::Unmarked { - match self { - TokenTree::Group(tt) => TokenTree::Group(tt.unmark()), - TokenTree::Punct(tt) => TokenTree::Punct(tt.unmark()), - TokenTree::Ident(tt) => TokenTree::Ident(tt.unmark()), - TokenTree::Literal(tt) => TokenTree::Literal(tt.unmark()), - } - } -} - -rpc_encode_decode!( - enum TokenTree<G, P, I, L> { - Group(tt), - Punct(tt), - Ident(tt), - Literal(tt), - } -); diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/rpc.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/rpc.rs deleted file mode 100644 index d50564d01..000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/rpc.rs +++ /dev/null @@ -1,305 +0,0 @@ -//! Serialization for client-server communication. - -use std::any::Any; -use std::char; -use std::io::Write; -use std::num::NonZeroU32; -use std::ops::Bound; -use std::str; - -pub(super) type Writer = super::buffer::Buffer<u8>; - -pub(super) trait Encode<S>: Sized { - fn encode(self, w: &mut Writer, s: &mut S); -} - -pub(super) type Reader<'a> = &'a [u8]; - -pub(super) trait Decode<'a, 's, S>: Sized { - fn decode(r: &mut Reader<'a>, s: &'s S) -> Self; -} - -pub(super) trait DecodeMut<'a, 's, S>: Sized { - fn decode(r: &mut Reader<'a>, s: &'s mut S) -> Self; -} - -macro_rules! rpc_encode_decode { - (le $ty:ty) => { - impl<S> Encode<S> for $ty { - fn encode(self, w: &mut Writer, _: &mut S) { - w.extend_from_array(&self.to_le_bytes()); - } - } - - impl<S> DecodeMut<'_, '_, S> for $ty { - fn decode(r: &mut Reader<'_>, _: &mut S) -> Self { - const N: usize = ::std::mem::size_of::<$ty>(); - - let mut bytes = [0; N]; - bytes.copy_from_slice(&r[..N]); - *r = &r[N..]; - - Self::from_le_bytes(bytes) - } - } - }; - (struct $name:ident { $($field:ident),* $(,)? }) => { - impl<S> Encode<S> for $name { - fn encode(self, w: &mut Writer, s: &mut S) { - $(self.$field.encode(w, s);)* - } - } - - impl<S> DecodeMut<'_, '_, S> for $name { - fn decode(r: &mut Reader<'_>, s: &mut S) -> Self { - $name { - $($field: DecodeMut::decode(r, s)),* - } - } - } - }; - (enum $name:ident $(<$($T:ident),+>)? { $($variant:ident $(($field:ident))*),* $(,)? }) => { - impl<S, $($($T: Encode<S>),+)?> Encode<S> for $name $(<$($T),+>)? { - fn encode(self, w: &mut Writer, s: &mut S) { - // HACK(eddyb): `Tag` enum duplicated between the - // two impls as there's no other place to stash it. - #[allow(non_upper_case_globals)] - mod tag { - #[repr(u8)] enum Tag { $($variant),* } - - $(pub const $variant: u8 = Tag::$variant as u8;)* - } - - match self { - $($name::$variant $(($field))* => { - tag::$variant.encode(w, s); - $($field.encode(w, s);)* - })* - } - } - } - - impl<'a, S, $($($T: for<'s> DecodeMut<'a, 's, S>),+)?> DecodeMut<'a, '_, S> - for $name $(<$($T),+>)? - { - fn decode(r: &mut Reader<'a>, s: &mut S) -> Self { - // HACK(eddyb): `Tag` enum duplicated between the - // two impls as there's no other place to stash it. - #[allow(non_upper_case_globals)] - mod tag { - #[repr(u8)] enum Tag { $($variant),* } - - $(pub const $variant: u8 = Tag::$variant as u8;)* - } - - match u8::decode(r, s) { - $(tag::$variant => { - $(let $field = DecodeMut::decode(r, s);)* - $name::$variant $(($field))* - })* - _ => unreachable!(), - } - } - } - } -} - -impl<S> Encode<S> for () { - fn encode(self, _: &mut Writer, _: &mut S) {} -} - -impl<S> DecodeMut<'_, '_, S> for () { - fn decode(_: &mut Reader<'_>, _: &mut S) -> Self {} -} - -impl<S> Encode<S> for u8 { - fn encode(self, w: &mut Writer, _: &mut S) { - w.push(self); - } -} - -impl<S> DecodeMut<'_, '_, S> for u8 { - fn decode(r: &mut Reader<'_>, _: &mut S) -> Self { - let x = r[0]; - *r = &r[1..]; - x - } -} - -rpc_encode_decode!(le u32); -rpc_encode_decode!(le usize); - -impl<S> Encode<S> for bool { - fn encode(self, w: &mut Writer, s: &mut S) { - (self as u8).encode(w, s); - } -} - -impl<S> DecodeMut<'_, '_, S> for bool { - fn decode(r: &mut Reader<'_>, s: &mut S) -> Self { - match u8::decode(r, s) { - 0 => false, - 1 => true, - _ => unreachable!(), - } - } -} - -impl<S> Encode<S> for char { - fn encode(self, w: &mut Writer, s: &mut S) { - (self as u32).encode(w, s); - } -} - -impl<S> DecodeMut<'_, '_, S> for char { - fn decode(r: &mut Reader<'_>, s: &mut S) -> Self { - char::from_u32(u32::decode(r, s)).unwrap() - } -} - -impl<S> Encode<S> for NonZeroU32 { - fn encode(self, w: &mut Writer, s: &mut S) { - self.get().encode(w, s); - } -} - -impl<S> DecodeMut<'_, '_, S> for NonZeroU32 { - fn decode(r: &mut Reader<'_>, s: &mut S) -> Self { - Self::new(u32::decode(r, s)).unwrap() - } -} - -impl<S, A: Encode<S>, B: Encode<S>> Encode<S> for (A, B) { - fn encode(self, w: &mut Writer, s: &mut S) { - self.0.encode(w, s); - self.1.encode(w, s); - } -} - -impl<'a, S, A: for<'s> DecodeMut<'a, 's, S>, B: for<'s> DecodeMut<'a, 's, S>> DecodeMut<'a, '_, S> - for (A, B) -{ - fn decode(r: &mut Reader<'a>, s: &mut S) -> Self { - (DecodeMut::decode(r, s), DecodeMut::decode(r, s)) - } -} - -rpc_encode_decode!( - enum Bound<T> { - Included(x), - Excluded(x), - Unbounded, - } -); - -rpc_encode_decode!( - enum Option<T> { - None, - Some(x), - } -); - -rpc_encode_decode!( - enum Result<T, E> { - Ok(x), - Err(e), - } -); - -impl<S> Encode<S> for &[u8] { - fn encode(self, w: &mut Writer, s: &mut S) { - self.len().encode(w, s); - w.write_all(self).unwrap(); - } -} - -impl<'a, S> DecodeMut<'a, '_, S> for &'a [u8] { - fn decode(r: &mut Reader<'a>, s: &mut S) -> Self { - let len = usize::decode(r, s); - let xs = &r[..len]; - *r = &r[len..]; - xs - } -} - -impl<S> Encode<S> for &str { - fn encode(self, w: &mut Writer, s: &mut S) { - self.as_bytes().encode(w, s); - } -} - -impl<'a, S> DecodeMut<'a, '_, S> for &'a str { - fn decode(r: &mut Reader<'a>, s: &mut S) -> Self { - str::from_utf8(<&[u8]>::decode(r, s)).unwrap() - } -} - -impl<S> Encode<S> for String { - fn encode(self, w: &mut Writer, s: &mut S) { - self[..].encode(w, s); - } -} - -impl<S> DecodeMut<'_, '_, S> for String { - fn decode(r: &mut Reader<'_>, s: &mut S) -> Self { - <&str>::decode(r, s).to_string() - } -} - -/// Simplified version of panic payloads, ignoring -/// types other than `&'static str` and `String`. -pub enum PanicMessage { - StaticStr(&'static str), - String(String), - Unknown, -} - -impl From<Box<dyn Any + Send>> for PanicMessage { - fn from(payload: Box<dyn Any + Send + 'static>) -> Self { - if let Some(s) = payload.downcast_ref::<&'static str>() { - return PanicMessage::StaticStr(s); - } - if let Ok(s) = payload.downcast::<String>() { - return PanicMessage::String(*s); - } - PanicMessage::Unknown - } -} - -impl Into<Box<dyn Any + Send>> for PanicMessage { - fn into(self) -> Box<dyn Any + Send> { - match self { - PanicMessage::StaticStr(s) => Box::new(s), - PanicMessage::String(s) => Box::new(s), - PanicMessage::Unknown => { - struct UnknownPanicMessage; - Box::new(UnknownPanicMessage) - } - } - } -} - -impl PanicMessage { - pub fn as_str(&self) -> Option<&str> { - match self { - PanicMessage::StaticStr(s) => Some(s), - PanicMessage::String(s) => Some(s), - PanicMessage::Unknown => None, - } - } -} - -impl<S> Encode<S> for PanicMessage { - fn encode(self, w: &mut Writer, s: &mut S) { - self.as_str().encode(w, s); - } -} - -impl<S> DecodeMut<'_, '_, S> for PanicMessage { - fn decode(r: &mut Reader<'_>, s: &mut S) -> Self { - match Option::<String>::decode(r, s) { - Some(s) => PanicMessage::String(s), - None => PanicMessage::Unknown, - } - } -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/scoped_cell.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/scoped_cell.rs deleted file mode 100644 index b0c2e5b9c..000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/scoped_cell.rs +++ /dev/null @@ -1,81 +0,0 @@ -//! `Cell` variant for (scoped) existential lifetimes. - -use std::cell::Cell; -use std::mem; -use std::ops::{Deref, DerefMut}; - -/// Type lambda application, with a lifetime. -#[allow(unused_lifetimes)] -pub trait ApplyL<'a> { - type Out; -} - -/// Type lambda taking a lifetime, i.e., `Lifetime -> Type`. -pub trait LambdaL: for<'a> ApplyL<'a> {} - -impl<T: for<'a> ApplyL<'a>> LambdaL for T {} - -// HACK(eddyb) work around projection limitations with a newtype -// FIXME(#52812) replace with `&'a mut <T as ApplyL<'b>>::Out` -pub struct RefMutL<'a, 'b, T: LambdaL>(&'a mut <T as ApplyL<'b>>::Out); - -impl<'a, 'b, T: LambdaL> Deref for RefMutL<'a, 'b, T> { - type Target = <T as ApplyL<'b>>::Out; - fn deref(&self) -> &Self::Target { - self.0 - } -} - -impl<'a, 'b, T: LambdaL> DerefMut for RefMutL<'a, 'b, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.0 - } -} - -pub struct ScopedCell<T: LambdaL>(Cell<<T as ApplyL<'static>>::Out>); - -impl<T: LambdaL> ScopedCell<T> { - pub fn new(value: <T as ApplyL<'static>>::Out) -> Self { - ScopedCell(Cell::new(value)) - } - - /// Sets the value in `self` to `replacement` while - /// running `f`, which gets the old value, mutably. - /// The old value will be restored after `f` exits, even - /// by panic, including modifications made to it by `f`. - pub fn replace<'a, R>( - &self, - replacement: <T as ApplyL<'a>>::Out, - f: impl for<'b, 'c> FnOnce(RefMutL<'b, 'c, T>) -> R, - ) -> R { - /// Wrapper that ensures that the cell always gets filled - /// (with the original state, optionally changed by `f`), - /// even if `f` had panicked. - struct PutBackOnDrop<'a, T: LambdaL> { - cell: &'a ScopedCell<T>, - value: Option<<T as ApplyL<'static>>::Out>, - } - - impl<'a, T: LambdaL> Drop for PutBackOnDrop<'a, T> { - fn drop(&mut self) { - self.cell.0.set(self.value.take().unwrap()); - } - } - - let mut put_back_on_drop = PutBackOnDrop { - cell: self, - value: Some(self.0.replace(unsafe { - let erased = mem::transmute_copy(&replacement); - mem::forget(replacement); - erased - })), - }; - - f(RefMutL(put_back_on_drop.value.as_mut().unwrap())) - } - - /// Sets the value in `self` to `value` while running `f`. - pub fn set<R>(&self, value: <T as ApplyL<'_>>::Out, f: impl FnOnce() -> R) -> R { - self.replace(value, |_| f()) - } -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/server.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/server.rs deleted file mode 100644 index 06a197913..000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/bridge/server.rs +++ /dev/null @@ -1,352 +0,0 @@ -//! Server-side traits. - -use super::*; - -// FIXME(eddyb) generate the definition of `HandleStore` in `server.rs`. -use super::client::HandleStore; - -/// Declare an associated item of one of the traits below, optionally -/// adjusting it (i.e., adding bounds to types and default bodies to methods). -macro_rules! associated_item { - (type FreeFunctions) => - (type FreeFunctions: 'static;); - (type TokenStream) => - (type TokenStream: 'static + Clone;); - (type TokenStreamBuilder) => - (type TokenStreamBuilder: 'static;); - (type TokenStreamIter) => - (type TokenStreamIter: 'static + Clone;); - (type Group) => - (type Group: 'static + Clone;); - (type Punct) => - (type Punct: 'static + Copy + Eq + Hash;); - (type Ident) => - (type Ident: 'static + Copy + Eq + Hash;); - (type Literal) => - (type Literal: 'static + Clone;); - (type SourceFile) => - (type SourceFile: 'static + Clone;); - (type MultiSpan) => - (type MultiSpan: 'static;); - (type Diagnostic) => - (type Diagnostic: 'static;); - (type Span) => - (type Span: 'static + Copy + Eq + Hash;); - (fn drop(&mut self, $arg:ident: $arg_ty:ty)) => - (fn drop(&mut self, $arg: $arg_ty) { mem::drop($arg) }); - (fn clone(&mut self, $arg:ident: $arg_ty:ty) -> $ret_ty:ty) => - (fn clone(&mut self, $arg: $arg_ty) -> $ret_ty { $arg.clone() }); - ($($item:tt)*) => ($($item)*;) -} - -macro_rules! declare_server_traits { - ($($name:ident { - $(fn $method:ident($($arg:ident: $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)?;)* - }),* $(,)?) => { - pub trait Types { - $(associated_item!(type $name);)* - } - - $(pub trait $name: Types { - $(associated_item!(fn $method(&mut self, $($arg: $arg_ty),*) $(-> $ret_ty)?);)* - })* - - pub trait Server: Types $(+ $name)* {} - impl<S: Types $(+ $name)*> Server for S {} - } -} -with_api!(Self, self_, declare_server_traits); - -pub(super) struct MarkedTypes<S: Types>(S); - -macro_rules! define_mark_types_impls { - ($($name:ident { - $(fn $method:ident($($arg:ident: $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)?;)* - }),* $(,)?) => { - impl<S: Types> Types for MarkedTypes<S> { - $(type $name = Marked<S::$name, client::$name>;)* - } - - $(impl<S: $name> $name for MarkedTypes<S> { - $(fn $method(&mut self, $($arg: $arg_ty),*) $(-> $ret_ty)? { - <_>::mark($name::$method(&mut self.0, $($arg.unmark()),*)) - })* - })* - } -} -with_api!(Self, self_, define_mark_types_impls); - -struct Dispatcher<S: Types> { - handle_store: HandleStore<S>, - server: S, -} - -macro_rules! define_dispatcher_impl { - ($($name:ident { - $(fn $method:ident($($arg:ident: $arg_ty:ty),* $(,)?) $(-> $ret_ty:ty)?;)* - }),* $(,)?) => { - // FIXME(eddyb) `pub` only for `ExecutionStrategy` below. - pub trait DispatcherTrait { - // HACK(eddyb) these are here to allow `Self::$name` to work below. - $(type $name;)* - fn dispatch(&mut self, b: Buffer<u8>) -> Buffer<u8>; - } - - impl<S: Server> DispatcherTrait for Dispatcher<MarkedTypes<S>> { - $(type $name = <MarkedTypes<S> as Types>::$name;)* - fn dispatch(&mut self, mut b: Buffer<u8>) -> Buffer<u8> { - let Dispatcher { handle_store, server } = self; - - let mut reader = &b[..]; - match api_tags::Method::decode(&mut reader, &mut ()) { - $(api_tags::Method::$name(m) => match m { - $(api_tags::$name::$method => { - let mut call_method = || { - reverse_decode!(reader, handle_store; $($arg: $arg_ty),*); - $name::$method(server, $($arg),*) - }; - // HACK(eddyb) don't use `panic::catch_unwind` in a panic. - // If client and server happen to use the same `libstd`, - // `catch_unwind` asserts that the panic counter was 0, - // even when the closure passed to it didn't panic. - let r = if thread::panicking() { - Ok(call_method()) - } else { - panic::catch_unwind(panic::AssertUnwindSafe(call_method)) - .map_err(PanicMessage::from) - }; - - b.clear(); - r.encode(&mut b, handle_store); - })* - }),* - } - b - } - } - } -} -with_api!(Self, self_, define_dispatcher_impl); - -pub trait ExecutionStrategy { - fn run_bridge_and_client<D: Copy + Send + 'static>( - &self, - dispatcher: &mut impl DispatcherTrait, - input: Buffer<u8>, - run_client: extern "C" fn(Bridge<'_>, D) -> Buffer<u8>, - client_data: D, - force_show_panics: bool, - ) -> Buffer<u8>; -} - -pub struct SameThread; - -impl ExecutionStrategy for SameThread { - fn run_bridge_and_client<D: Copy + Send + 'static>( - &self, - dispatcher: &mut impl DispatcherTrait, - input: Buffer<u8>, - run_client: extern "C" fn(Bridge<'_>, D) -> Buffer<u8>, - client_data: D, - force_show_panics: bool, - ) -> Buffer<u8> { - let mut dispatch = |b| dispatcher.dispatch(b); - - run_client( - Bridge { cached_buffer: input, dispatch: (&mut dispatch).into(), force_show_panics }, - client_data, - ) - } -} - -// NOTE(eddyb) Two implementations are provided, the second one is a bit -// faster but neither is anywhere near as fast as same-thread execution. - -pub struct CrossThread1; - -impl ExecutionStrategy for CrossThread1 { - fn run_bridge_and_client<D: Copy + Send + 'static>( - &self, - dispatcher: &mut impl DispatcherTrait, - input: Buffer<u8>, - run_client: extern "C" fn(Bridge<'_>, D) -> Buffer<u8>, - client_data: D, - force_show_panics: bool, - ) -> Buffer<u8> { - use std::sync::mpsc::channel; - - let (req_tx, req_rx) = channel(); - let (res_tx, res_rx) = channel(); - - let join_handle = thread::spawn(move || { - let mut dispatch = |b| { - req_tx.send(b).unwrap(); - res_rx.recv().unwrap() - }; - - run_client( - Bridge { - cached_buffer: input, - dispatch: (&mut dispatch).into(), - force_show_panics, - }, - client_data, - ) - }); - - for b in req_rx { - res_tx.send(dispatcher.dispatch(b)).unwrap(); - } - - join_handle.join().unwrap() - } -} - -pub struct CrossThread2; - -impl ExecutionStrategy for CrossThread2 { - fn run_bridge_and_client<D: Copy + Send + 'static>( - &self, - dispatcher: &mut impl DispatcherTrait, - input: Buffer<u8>, - run_client: extern "C" fn(Bridge<'_>, D) -> Buffer<u8>, - client_data: D, - force_show_panics: bool, - ) -> Buffer<u8> { - use std::sync::{Arc, Mutex}; - - enum State<T> { - Req(T), - Res(T), - } - - let mut state = Arc::new(Mutex::new(State::Res(Buffer::new()))); - - let server_thread = thread::current(); - let state2 = state.clone(); - let join_handle = thread::spawn(move || { - let mut dispatch = |b| { - *state2.lock().unwrap() = State::Req(b); - server_thread.unpark(); - loop { - thread::park(); - if let State::Res(b) = &mut *state2.lock().unwrap() { - break b.take(); - } - } - }; - - let r = run_client( - Bridge { - cached_buffer: input, - dispatch: (&mut dispatch).into(), - force_show_panics, - }, - client_data, - ); - - // Wake up the server so it can exit the dispatch loop. - drop(state2); - server_thread.unpark(); - - r - }); - - // Check whether `state2` was dropped, to know when to stop. - while Arc::get_mut(&mut state).is_none() { - thread::park(); - let mut b = match &mut *state.lock().unwrap() { - State::Req(b) => b.take(), - _ => continue, - }; - b = dispatcher.dispatch(b.take()); - *state.lock().unwrap() = State::Res(b); - join_handle.thread().unpark(); - } - - join_handle.join().unwrap() - } -} - -fn run_server< - S: Server, - I: Encode<HandleStore<MarkedTypes<S>>>, - O: for<'a, 's> DecodeMut<'a, 's, HandleStore<MarkedTypes<S>>>, - D: Copy + Send + 'static, ->( - strategy: &impl ExecutionStrategy, - handle_counters: &'static client::HandleCounters, - server: S, - input: I, - run_client: extern "C" fn(Bridge<'_>, D) -> Buffer<u8>, - client_data: D, - force_show_panics: bool, -) -> Result<O, PanicMessage> { - let mut dispatcher = - Dispatcher { handle_store: HandleStore::new(handle_counters), server: MarkedTypes(server) }; - - let mut b = Buffer::new(); - input.encode(&mut b, &mut dispatcher.handle_store); - - b = strategy.run_bridge_and_client( - &mut dispatcher, - b, - run_client, - client_data, - force_show_panics, - ); - - Result::decode(&mut &b[..], &mut dispatcher.handle_store) -} - -impl client::Client<fn(super::super::TokenStream) -> super::super::TokenStream> { - pub fn run<S: Server>( - &self, - strategy: &impl ExecutionStrategy, - server: S, - input: S::TokenStream, - force_show_panics: bool, - ) -> Result<S::TokenStream, PanicMessage> { - let client::Client { get_handle_counters, run, f } = *self; - run_server( - strategy, - get_handle_counters(), - server, - <MarkedTypes<S> as Types>::TokenStream::mark(input), - run, - f, - force_show_panics, - ) - .map(<MarkedTypes<S> as Types>::TokenStream::unmark) - } -} - -impl - client::Client< - fn(super::super::TokenStream, super::super::TokenStream) -> super::super::TokenStream, - > -{ - pub fn run<S: Server>( - &self, - strategy: &impl ExecutionStrategy, - server: S, - input: S::TokenStream, - input2: S::TokenStream, - force_show_panics: bool, - ) -> Result<S::TokenStream, PanicMessage> { - let client::Client { get_handle_counters, run, f } = *self; - run_server( - strategy, - get_handle_counters(), - server, - ( - <MarkedTypes<S> as Types>::TokenStream::mark(input), - <MarkedTypes<S> as Types>::TokenStream::mark(input2), - ), - run, - f, - force_show_panics, - ) - .map(<MarkedTypes<S> as Types>::TokenStream::unmark) - } -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/diagnostic.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/diagnostic.rs deleted file mode 100644 index cda239f87..000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/diagnostic.rs +++ /dev/null @@ -1,166 +0,0 @@ -//! lib-proc-macro diagnostic -//! -//! Copy from <https://github.com/rust-lang/rust/blob/6050e523bae6de61de4e060facc43dc512adaccd/src/libproc_macro/diagnostic.rs> -//! augmented with removing unstable features - -use super::Span; - -/// An enum representing a diagnostic level. -#[derive(Copy, Clone, Debug)] -#[non_exhaustive] -pub enum Level { - /// An error. - Error, - /// A warning. - Warning, - /// A note. - Note, - /// A help message. - Help, -} - -/// Trait implemented by types that can be converted into a set of `Span`s. -pub trait MultiSpan { - /// Converts `self` into a `Vec<Span>`. - fn into_spans(self) -> Vec<Span>; -} - -impl MultiSpan for Span { - fn into_spans(self) -> Vec<Span> { - vec![self] - } -} - -impl MultiSpan for Vec<Span> { - fn into_spans(self) -> Vec<Span> { - self - } -} - -impl<'a> MultiSpan for &'a [Span] { - fn into_spans(self) -> Vec<Span> { - self.to_vec() - } -} - -/// A structure representing a diagnostic message and associated children -/// messages. -#[derive(Clone, Debug)] -pub struct Diagnostic { - level: Level, - message: String, - spans: Vec<Span>, - children: Vec<Diagnostic>, -} - -macro_rules! diagnostic_child_methods { - ($spanned:ident, $regular:ident, $level:expr) => { - #[doc = concat!("Adds a new child diagnostics message to `self` with the [`", - stringify!($level), "`] level, and the given `spans` and `message`.")] - pub fn $spanned<S, T>(mut self, spans: S, message: T) -> Diagnostic - where - S: MultiSpan, - T: Into<String>, - { - self.children.push(Diagnostic::spanned(spans, $level, message)); - self - } - - #[doc = concat!("Adds a new child diagnostic message to `self` with the [`", - stringify!($level), "`] level, and the given `message`.")] - pub fn $regular<T: Into<String>>(mut self, message: T) -> Diagnostic { - self.children.push(Diagnostic::new($level, message)); - self - } - }; -} - -/// Iterator over the children diagnostics of a `Diagnostic`. -#[derive(Debug, Clone)] -pub struct Children<'a>(std::slice::Iter<'a, Diagnostic>); - -impl<'a> Iterator for Children<'a> { - type Item = &'a Diagnostic; - - fn next(&mut self) -> Option<Self::Item> { - self.0.next() - } -} - -impl Diagnostic { - /// Creates a new diagnostic with the given `level` and `message`. - pub fn new<T: Into<String>>(level: Level, message: T) -> Diagnostic { - Diagnostic { level, message: message.into(), spans: vec![], children: vec![] } - } - - /// Creates a new diagnostic with the given `level` and `message` pointing to - /// the given set of `spans`. - pub fn spanned<S, T>(spans: S, level: Level, message: T) -> Diagnostic - where - S: MultiSpan, - T: Into<String>, - { - Diagnostic { level, message: message.into(), spans: spans.into_spans(), children: vec![] } - } - - diagnostic_child_methods!(span_error, error, Level::Error); - diagnostic_child_methods!(span_warning, warning, Level::Warning); - diagnostic_child_methods!(span_note, note, Level::Note); - diagnostic_child_methods!(span_help, help, Level::Help); - - /// Returns the diagnostic `level` for `self`. - pub fn level(&self) -> Level { - self.level - } - - /// Sets the level in `self` to `level`. - pub fn set_level(&mut self, level: Level) { - self.level = level; - } - - /// Returns the message in `self`. - pub fn message(&self) -> &str { - &self.message - } - - /// Sets the message in `self` to `message`. - pub fn set_message<T: Into<String>>(&mut self, message: T) { - self.message = message.into(); - } - - /// Returns the `Span`s in `self`. - pub fn spans(&self) -> &[Span] { - &self.spans - } - - /// Sets the `Span`s in `self` to `spans`. - pub fn set_spans<S: MultiSpan>(&mut self, spans: S) { - self.spans = spans.into_spans(); - } - - /// Returns an iterator over the children diagnostics of `self`. - pub fn children(&self) -> Children<'_> { - Children(self.children.iter()) - } - - /// Emit the diagnostic. - pub fn emit(self) { - fn to_internal(spans: Vec<Span>) -> super::bridge::client::MultiSpan { - let mut multi_span = super::bridge::client::MultiSpan::new(); - for span in spans { - multi_span.push(span.0); - } - multi_span - } - - let mut diag = super::bridge::client::Diagnostic::new( - self.level, - &self.message[..], - to_internal(self.spans), - ); - for c in self.children { - diag.sub(c.level, &c.message[..], to_internal(c.spans)); - } - diag.emit(); - } -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/mod.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/mod.rs deleted file mode 100644 index c5145d00e..000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/mod.rs +++ /dev/null @@ -1,1056 +0,0 @@ -//! A support library for macro authors when defining new macros. -//! -//! This library, provided by the standard distribution, provides the types -//! consumed in the interfaces of procedurally defined macro definitions such as -//! function-like macros `#[proc_macro]`, macro attributes `#[proc_macro_attribute]` and -//! custom derive attributes`#[proc_macro_derive]`. -//! -//! See [the book] for more. -//! -//! [the book]: ../book/ch19-06-macros.html#procedural-macros-for-generating-code-from-attributes - -#[doc(hidden)] -pub mod bridge; - -mod diagnostic; - -pub use diagnostic::{Diagnostic, Level, MultiSpan}; - -use std::cmp::Ordering; -use std::ops::RangeBounds; -use std::path::PathBuf; -use std::str::FromStr; -use std::{error, fmt, iter, mem}; - -/// Determines whether proc_macro has been made accessible to the currently -/// running program. -/// -/// The proc_macro crate is only intended for use inside the implementation of -/// procedural macros. All the functions in this crate panic if invoked from -/// outside of a procedural macro, such as from a build script or unit test or -/// ordinary Rust binary. -/// -/// With consideration for Rust libraries that are designed to support both -/// macro and non-macro use cases, `proc_macro::is_available()` provides a -/// non-panicking way to detect whether the infrastructure required to use the -/// API of proc_macro is presently available. Returns true if invoked from -/// inside of a procedural macro, false if invoked from any other binary. -pub fn is_available() -> bool { - bridge::Bridge::is_available() -} - -/// The main type provided by this crate, representing an abstract stream of -/// tokens, or, more specifically, a sequence of token trees. -/// The type provide interfaces for iterating over those token trees and, conversely, -/// collecting a number of token trees into one stream. -/// -/// This is both the input and output of `#[proc_macro]`, `#[proc_macro_attribute]` -/// and `#[proc_macro_derive]` definitions. -#[derive(Clone)] -pub struct TokenStream(bridge::client::TokenStream); - -/// Error returned from `TokenStream::from_str`. -#[non_exhaustive] -#[derive(Debug)] -pub struct LexError; - -impl fmt::Display for LexError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("cannot parse string into token stream") - } -} - -impl error::Error for LexError {} - -/// Error returned from `TokenStream::expand_expr`. -#[non_exhaustive] -#[derive(Debug)] -pub struct ExpandError; - -impl fmt::Display for ExpandError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("macro expansion failed") - } -} - -impl error::Error for ExpandError {} - -impl TokenStream { - /// Returns an empty `TokenStream` containing no token trees. - pub fn new() -> TokenStream { - TokenStream(bridge::client::TokenStream::new()) - } - - /// Checks if this `TokenStream` is empty. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Parses this `TokenStream` as an expression and attempts to expand any - /// macros within it. Returns the expanded `TokenStream`. - /// - /// Currently only expressions expanding to literals will succeed, although - /// this may be relaxed in the future. - /// - /// NOTE: In error conditions, `expand_expr` may leave macros unexpanded, - /// report an error, failing compilation, and/or return an `Err(..)`. The - /// specific behavior for any error condition, and what conditions are - /// considered errors, is unspecified and may change in the future. - pub fn expand_expr(&self) -> Result<TokenStream, ExpandError> { - match bridge::client::TokenStream::expand_expr(&self.0) { - Ok(stream) => Ok(TokenStream(stream)), - Err(_) => Err(ExpandError), - } - } -} - -/// Attempts to break the string into tokens and parse those tokens into a token stream. -/// May fail for a number of reasons, for example, if the string contains unbalanced delimiters -/// or characters not existing in the language. -/// All tokens in the parsed stream get `Span::call_site()` spans. -/// -/// NOTE: some errors may cause panics instead of returning `LexError`. We reserve the right to -/// change these errors into `LexError`s later. -impl FromStr for TokenStream { - type Err = LexError; - - fn from_str(src: &str) -> Result<TokenStream, LexError> { - Ok(TokenStream(bridge::client::TokenStream::from_str(src))) - } -} - -/// Prints the token stream as a string that is supposed to be losslessly convertible back -/// into the same token stream (modulo spans), except for possibly `TokenTree::Group`s -/// with `Delimiter::None` delimiters and negative numeric literals. -impl fmt::Display for TokenStream { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.to_string()) - } -} - -/// Prints token in a form convenient for debugging. -impl fmt::Debug for TokenStream { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("TokenStream ")?; - f.debug_list().entries(self.clone()).finish() - } -} - -impl Default for TokenStream { - fn default() -> Self { - TokenStream::new() - } -} - -pub use quote::{quote, quote_span}; - -/// Creates a token stream containing a single token tree. -impl From<TokenTree> for TokenStream { - fn from(tree: TokenTree) -> TokenStream { - TokenStream(bridge::client::TokenStream::from_token_tree(match tree { - TokenTree::Group(tt) => bridge::TokenTree::Group(tt.0), - TokenTree::Punct(tt) => bridge::TokenTree::Punct(tt.0), - TokenTree::Ident(tt) => bridge::TokenTree::Ident(tt.0), - TokenTree::Literal(tt) => bridge::TokenTree::Literal(tt.0), - })) - } -} - -/// Collects a number of token trees into a single stream. -impl FromIterator<TokenTree> for TokenStream { - fn from_iter<I: IntoIterator<Item = TokenTree>>(trees: I) -> Self { - trees.into_iter().map(TokenStream::from).collect() - } -} - -/// A "flattening" operation on token streams, collects token trees -/// from multiple token streams into a single stream. -impl FromIterator<TokenStream> for TokenStream { - fn from_iter<I: IntoIterator<Item = TokenStream>>(streams: I) -> Self { - let mut builder = bridge::client::TokenStreamBuilder::new(); - streams.into_iter().for_each(|stream| builder.push(stream.0)); - TokenStream(builder.build()) - } -} - -impl Extend<TokenTree> for TokenStream { - fn extend<I: IntoIterator<Item = TokenTree>>(&mut self, trees: I) { - self.extend(trees.into_iter().map(TokenStream::from)); - } -} - -impl Extend<TokenStream> for TokenStream { - fn extend<I: IntoIterator<Item = TokenStream>>(&mut self, streams: I) { - // FIXME(eddyb) Use an optimized implementation if/when possible. - *self = iter::once(mem::replace(self, Self::new())).chain(streams).collect(); - } -} - -/// Public implementation details for the `TokenStream` type, such as iterators. -pub mod token_stream { - use super::{bridge, Group, Ident, Literal, Punct, TokenStream, TokenTree}; - - /// An iterator over `TokenStream`'s `TokenTree`s. - /// The iteration is "shallow", e.g., the iterator doesn't recurse into delimited groups, - /// and returns whole groups as token trees. - #[derive(Clone)] - pub struct IntoIter(bridge::client::TokenStreamIter); - - impl Iterator for IntoIter { - type Item = TokenTree; - - fn next(&mut self) -> Option<TokenTree> { - self.0.next().map(|tree| match tree { - bridge::TokenTree::Group(tt) => TokenTree::Group(Group(tt)), - bridge::TokenTree::Punct(tt) => TokenTree::Punct(Punct(tt)), - bridge::TokenTree::Ident(tt) => TokenTree::Ident(Ident(tt)), - bridge::TokenTree::Literal(tt) => TokenTree::Literal(Literal(tt)), - }) - } - } - - impl IntoIterator for TokenStream { - type Item = TokenTree; - type IntoIter = IntoIter; - - fn into_iter(self) -> IntoIter { - IntoIter(self.0.into_iter()) - } - } -} - -/// `quote!(..)` accepts arbitrary tokens and expands into a `TokenStream` describing the input. -/// For example, `quote!(a + b)` will produce an expression, that, when evaluated, constructs -/// the `TokenStream` `[Ident("a"), Punct('+', Alone), Ident("b")]`. -/// -/// Unquoting is done with `$`, and works by taking the single next ident as the unquoted term. -/// To quote `$` itself, use `$$`. -//pub macro quote($($t:tt)*) { -//[> compiler built-in <] -//} - -#[doc(hidden)] -mod quote; - -/// A region of source code, along with macro expansion information. -#[derive(Copy, Clone)] -pub struct Span(bridge::client::Span); - -macro_rules! diagnostic_method { - ($name:ident, $level:expr) => { - /// Creates a new `Diagnostic` with the given `message` at the span - /// `self`. - pub fn $name<T: Into<String>>(self, message: T) -> Diagnostic { - Diagnostic::spanned(self, $level, message) - } - }; -} - -impl Span { - /// A span that resolves at the macro definition site. - pub fn def_site() -> Span { - Span(bridge::client::Span::def_site()) - } - - /// The span of the invocation of the current procedural macro. - /// Identifiers created with this span will be resolved as if they were written - /// directly at the macro call location (call-site hygiene) and other code - /// at the macro call site will be able to refer to them as well. - pub fn call_site() -> Span { - Span(bridge::client::Span::call_site()) - } - - /// A span that represents `macro_rules` hygiene, and sometimes resolves at the macro - /// definition site (local variables, labels, `$crate`) and sometimes at the macro - /// call site (everything else). - /// The span location is taken from the call-site. - pub fn mixed_site() -> Span { - Span(bridge::client::Span::mixed_site()) - } - - /// The original source file into which this span points. - pub fn source_file(&self) -> SourceFile { - SourceFile(self.0.source_file()) - } - - /// The `Span` for the tokens in the previous macro expansion from which - /// `self` was generated from, if any. - pub fn parent(&self) -> Option<Span> { - self.0.parent().map(Span) - } - - /// The span for the origin source code that `self` was generated from. If - /// this `Span` wasn't generated from other macro expansions then the return - /// value is the same as `*self`. - pub fn source(&self) -> Span { - Span(self.0.source()) - } - - /// Gets the starting line/column in the source file for this span. - pub fn start(&self) -> LineColumn { - self.0.start().add_1_to_column() - } - - /// Gets the ending line/column in the source file for this span. - pub fn end(&self) -> LineColumn { - self.0.end().add_1_to_column() - } - - /// Creates an empty span pointing to directly before this span. - pub fn before(&self) -> Span { - Span(self.0.before()) - } - - /// Creates an empty span pointing to directly after this span. - pub fn after(&self) -> Span { - Span(self.0.after()) - } - - /// Creates a new span encompassing `self` and `other`. - /// - /// Returns `None` if `self` and `other` are from different files. - pub fn join(&self, other: Span) -> Option<Span> { - self.0.join(other.0).map(Span) - } - - /// Creates a new span with the same line/column information as `self` but - /// that resolves symbols as though it were at `other`. - pub fn resolved_at(&self, other: Span) -> Span { - Span(self.0.resolved_at(other.0)) - } - - /// Creates a new span with the same name resolution behavior as `self` but - /// with the line/column information of `other`. - pub fn located_at(&self, other: Span) -> Span { - other.resolved_at(*self) - } - - /// Compares to spans to see if they're equal. - pub fn eq(&self, other: &Span) -> bool { - self.0 == other.0 - } - - /// Returns the source text behind a span. This preserves the original source - /// code, including spaces and comments. It only returns a result if the span - /// corresponds to real source code. - /// - /// Note: The observable result of a macro should only rely on the tokens and - /// not on this source text. The result of this function is a best effort to - /// be used for diagnostics only. - pub fn source_text(&self) -> Option<String> { - self.0.source_text() - } - - // Used by the implementation of `Span::quote` - #[doc(hidden)] - pub fn save_span(&self) -> usize { - self.0.save_span() - } - - // Used by the implementation of `Span::quote` - #[doc(hidden)] - pub fn recover_proc_macro_span(id: usize) -> Span { - Span(bridge::client::Span::recover_proc_macro_span(id)) - } - - diagnostic_method!(error, Level::Error); - diagnostic_method!(warning, Level::Warning); - diagnostic_method!(note, Level::Note); - diagnostic_method!(help, Level::Help); -} - -/// Prints a span in a form convenient for debugging. -impl fmt::Debug for Span { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -/// A line-column pair representing the start or end of a `Span`. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct LineColumn { - /// The 1-indexed line in the source file on which the span starts or ends (inclusive). - pub line: usize, - /// The 1-indexed column (number of bytes in UTF-8 encoding) in the source - /// file on which the span starts or ends (inclusive). - pub column: usize, -} - -impl LineColumn { - fn add_1_to_column(self) -> Self { - LineColumn { line: self.line, column: self.column + 1 } - } -} - -impl Ord for LineColumn { - fn cmp(&self, other: &Self) -> Ordering { - self.line.cmp(&other.line).then(self.column.cmp(&other.column)) - } -} - -impl PartialOrd for LineColumn { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - Some(self.cmp(other)) - } -} - -/// The source file of a given `Span`. -#[derive(Clone)] -pub struct SourceFile(bridge::client::SourceFile); - -impl SourceFile { - /// Gets the path to this source file. - /// - /// ### Note - /// If the code span associated with this `SourceFile` was generated by an external macro, this - /// macro, this might not be an actual path on the filesystem. Use [`is_real`] to check. - /// - /// Also note that even if `is_real` returns `true`, if `--remap-path-prefix` was passed on - /// the command line, the path as given might not actually be valid. - /// - /// [`is_real`]: Self::is_real - pub fn path(&self) -> PathBuf { - PathBuf::from(self.0.path()) - } - - /// Returns `true` if this source file is a real source file, and not generated by an external - /// macro's expansion. - pub fn is_real(&self) -> bool { - // This is a hack until intercrate spans are implemented and we can have real source files - // for spans generated in external macros. - // https://github.com/rust-lang/rust/pull/43604#issuecomment-333334368 - self.0.is_real() - } -} - -impl fmt::Debug for SourceFile { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("SourceFile") - .field("path", &self.path()) - .field("is_real", &self.is_real()) - .finish() - } -} - -impl PartialEq for SourceFile { - fn eq(&self, other: &Self) -> bool { - self.0.eq(&other.0) - } -} - -impl Eq for SourceFile {} - -/// A single token or a delimited sequence of token trees (e.g., `[1, (), ..]`). -#[derive(Clone)] -pub enum TokenTree { - /// A token stream surrounded by bracket delimiters. - Group(Group), - /// An identifier. - Ident(Ident), - /// A single punctuation character (`+`, `,`, `$`, etc.). - Punct(Punct), - /// A literal character (`'a'`), string (`"hello"`), number (`2.3`), etc. - Literal(Literal), -} - -impl TokenTree { - /// Returns the span of this tree, delegating to the `span` method of - /// the contained token or a delimited stream. - pub fn span(&self) -> Span { - match *self { - TokenTree::Group(ref t) => t.span(), - TokenTree::Ident(ref t) => t.span(), - TokenTree::Punct(ref t) => t.span(), - TokenTree::Literal(ref t) => t.span(), - } - } - - /// Configures the span for *only this token*. - /// - /// Note that if this token is a `Group` then this method will not configure - /// the span of each of the internal tokens, this will simply delegate to - /// the `set_span` method of each variant. - pub fn set_span(&mut self, span: Span) { - match *self { - TokenTree::Group(ref mut t) => t.set_span(span), - TokenTree::Ident(ref mut t) => t.set_span(span), - TokenTree::Punct(ref mut t) => t.set_span(span), - TokenTree::Literal(ref mut t) => t.set_span(span), - } - } -} - -/// Prints token tree in a form convenient for debugging. -impl fmt::Debug for TokenTree { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Each of these has the name in the struct type in the derived debug, - // so don't bother with an extra layer of indirection - match *self { - TokenTree::Group(ref tt) => tt.fmt(f), - TokenTree::Ident(ref tt) => tt.fmt(f), - TokenTree::Punct(ref tt) => tt.fmt(f), - TokenTree::Literal(ref tt) => tt.fmt(f), - } - } -} - -impl From<Group> for TokenTree { - fn from(g: Group) -> TokenTree { - TokenTree::Group(g) - } -} - -impl From<Ident> for TokenTree { - fn from(g: Ident) -> TokenTree { - TokenTree::Ident(g) - } -} - -impl From<Punct> for TokenTree { - fn from(g: Punct) -> TokenTree { - TokenTree::Punct(g) - } -} - -impl From<Literal> for TokenTree { - fn from(g: Literal) -> TokenTree { - TokenTree::Literal(g) - } -} - -/// Prints the token tree as a string that is supposed to be losslessly convertible back -/// into the same token tree (modulo spans), except for possibly `TokenTree::Group`s -/// with `Delimiter::None` delimiters and negative numeric literals. -impl fmt::Display for TokenTree { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.to_string()) - } -} - -/// A delimited token stream. -/// -/// A `Group` internally contains a `TokenStream` which is surrounded by `Delimiter`s. -#[derive(Clone)] -pub struct Group(bridge::client::Group); - -/// Describes how a sequence of token trees is delimited. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Delimiter { - /// `( ... )` - Parenthesis, - /// `{ ... }` - Brace, - /// `[ ... ]` - Bracket, - /// `Ø ... Ø` - /// An implicit delimiter, that may, for example, appear around tokens coming from a - /// "macro variable" `$var`. It is important to preserve operator priorities in cases like - /// `$var * 3` where `$var` is `1 + 2`. - /// Implicit delimiters might not survive roundtrip of a token stream through a string. - None, -} - -impl Group { - /// Creates a new `Group` with the given delimiter and token stream. - /// - /// This constructor will set the span for this group to - /// `Span::call_site()`. To change the span you can use the `set_span` - /// method below. - pub fn new(delimiter: Delimiter, stream: TokenStream) -> Group { - Group(bridge::client::Group::new(delimiter, stream.0)) - } - - /// Returns the delimiter of this `Group` - pub fn delimiter(&self) -> Delimiter { - self.0.delimiter() - } - - /// Returns the `TokenStream` of tokens that are delimited in this `Group`. - /// - /// Note that the returned token stream does not include the delimiter - /// returned above. - pub fn stream(&self) -> TokenStream { - TokenStream(self.0.stream()) - } - - /// Returns the span for the delimiters of this token stream, spanning the - /// entire `Group`. - /// - /// ```text - /// pub fn span(&self) -> Span { - /// ^^^^^^^ - /// ``` - pub fn span(&self) -> Span { - Span(self.0.span()) - } - - /// Returns the span pointing to the opening delimiter of this group. - /// - /// ```text - /// pub fn span_open(&self) -> Span { - /// ^ - /// ``` - pub fn span_open(&self) -> Span { - Span(self.0.span_open()) - } - - /// Returns the span pointing to the closing delimiter of this group. - /// - /// ```text - /// pub fn span_close(&self) -> Span { - /// ^ - /// ``` - pub fn span_close(&self) -> Span { - Span(self.0.span_close()) - } - - /// Configures the span for this `Group`'s delimiters, but not its internal - /// tokens. - /// - /// This method will **not** set the span of all the internal tokens spanned - /// by this group, but rather it will only set the span of the delimiter - /// tokens at the level of the `Group`. - pub fn set_span(&mut self, span: Span) { - self.0.set_span(span.0); - } -} - -/// Prints the group as a string that should be losslessly convertible back -/// into the same group (modulo spans), except for possibly `TokenTree::Group`s -/// with `Delimiter::None` delimiters. -impl fmt::Display for Group { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.to_string()) - } -} - -impl fmt::Debug for Group { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Group") - .field("delimiter", &self.delimiter()) - .field("stream", &self.stream()) - .field("span", &self.span()) - .finish() - } -} - -/// A `Punct` is a single punctuation character such as `+`, `-` or `#`. -/// -/// Multi-character operators like `+=` are represented as two instances of `Punct` with different -/// forms of `Spacing` returned. -#[derive(Clone)] -pub struct Punct(bridge::client::Punct); - -/// Describes whether a `Punct` is followed immediately by another `Punct` ([`Spacing::Joint`]) or -/// by a different token or whitespace ([`Spacing::Alone`]). -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Spacing { - /// A `Punct` is not immediately followed by another `Punct`. - /// E.g. `+` is `Alone` in `+ =`, `+ident` and `+()`. - Alone, - /// A `Punct` is immediately followed by another `Punct`. - /// E.g. `+` is `Joint` in `+=` and `++`. - /// - /// Additionally, single quote `'` can join with identifiers to form lifetimes: `'ident`. - Joint, -} - -impl Punct { - /// Creates a new `Punct` from the given character and spacing. - /// The `ch` argument must be a valid punctuation character permitted by the language, - /// otherwise the function will panic. - /// - /// The returned `Punct` will have the default span of `Span::call_site()` - /// which can be further configured with the `set_span` method below. - pub fn new(ch: char, spacing: Spacing) -> Punct { - Punct(bridge::client::Punct::new(ch, spacing)) - } - - /// Returns the value of this punctuation character as `char`. - pub fn as_char(&self) -> char { - self.0.as_char() - } - - /// Returns the spacing of this punctuation character, indicating whether it's immediately - /// followed by another `Punct` in the token stream, so they can potentially be combined into - /// a multi-character operator (`Joint`), or it's followed by some other token or whitespace - /// (`Alone`) so the operator has certainly ended. - pub fn spacing(&self) -> Spacing { - self.0.spacing() - } - - /// Returns the span for this punctuation character. - pub fn span(&self) -> Span { - Span(self.0.span()) - } - - /// Configure the span for this punctuation character. - pub fn set_span(&mut self, span: Span) { - self.0 = self.0.with_span(span.0); - } -} - -/// Prints the punctuation character as a string that should be losslessly convertible -/// back into the same character. -impl fmt::Display for Punct { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.to_string()) - } -} - -impl fmt::Debug for Punct { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Punct") - .field("ch", &self.as_char()) - .field("spacing", &self.spacing()) - .field("span", &self.span()) - .finish() - } -} - -impl PartialEq<char> for Punct { - fn eq(&self, rhs: &char) -> bool { - self.as_char() == *rhs - } -} - -impl PartialEq<Punct> for char { - fn eq(&self, rhs: &Punct) -> bool { - *self == rhs.as_char() - } -} - -/// An identifier (`ident`). -#[derive(Clone)] -pub struct Ident(bridge::client::Ident); - -impl Ident { - /// Creates a new `Ident` with the given `string` as well as the specified - /// `span`. - /// The `string` argument must be a valid identifier permitted by the - /// language (including keywords, e.g. `self` or `fn`). Otherwise, the function will panic. - /// - /// Note that `span`, currently in rustc, configures the hygiene information - /// for this identifier. - /// - /// As of this time `Span::call_site()` explicitly opts-in to "call-site" hygiene - /// meaning that identifiers created with this span will be resolved as if they were written - /// directly at the location of the macro call, and other code at the macro call site will be - /// able to refer to them as well. - /// - /// Later spans like `Span::def_site()` will allow to opt-in to "definition-site" hygiene - /// meaning that identifiers created with this span will be resolved at the location of the - /// macro definition and other code at the macro call site will not be able to refer to them. - /// - /// Due to the current importance of hygiene this constructor, unlike other - /// tokens, requires a `Span` to be specified at construction. - pub fn new(string: &str, span: Span) -> Ident { - Ident(bridge::client::Ident::new(string, span.0, false)) - } - - /// Same as `Ident::new`, but creates a raw identifier (`r#ident`). - /// The `string` argument be a valid identifier permitted by the language - /// (including keywords, e.g. `fn`). Keywords which are usable in path segments - /// (e.g. `self`, `super`) are not supported, and will cause a panic. - pub fn new_raw(string: &str, span: Span) -> Ident { - Ident(bridge::client::Ident::new(string, span.0, true)) - } - - /// Returns the span of this `Ident`, encompassing the entire string returned - /// by [`to_string`](Self::to_string). - pub fn span(&self) -> Span { - Span(self.0.span()) - } - - /// Configures the span of this `Ident`, possibly changing its hygiene context. - pub fn set_span(&mut self, span: Span) { - self.0 = self.0.with_span(span.0); - } -} - -/// Prints the identifier as a string that should be losslessly convertible -/// back into the same identifier. -impl fmt::Display for Ident { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.to_string()) - } -} - -impl fmt::Debug for Ident { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Ident") - .field("ident", &self.to_string()) - .field("span", &self.span()) - .finish() - } -} - -/// A literal string (`"hello"`), byte string (`b"hello"`), -/// character (`'a'`), byte character (`b'a'`), an integer or floating point number -/// with or without a suffix (`1`, `1u8`, `2.3`, `2.3f32`). -/// Boolean literals like `true` and `false` do not belong here, they are `Ident`s. -#[derive(Clone)] -pub struct Literal(bridge::client::Literal); - -macro_rules! suffixed_int_literals { - ($($name:ident => $kind:ident,)*) => ($( - /// Creates a new suffixed integer literal with the specified value. - /// - /// This function will create an integer like `1u32` where the integer - /// value specified is the first part of the token and the integral is - /// also suffixed at the end. - /// Literals created from negative numbers might not survive round-trips through - /// `TokenStream` or strings and may be broken into two tokens (`-` and positive literal). - /// - /// Literals created through this method have the `Span::call_site()` - /// span by default, which can be configured with the `set_span` method - /// below. - pub fn $name(n: $kind) -> Literal { - Literal(bridge::client::Literal::typed_integer(&n.to_string(), stringify!($kind))) - } - )*) -} - -macro_rules! unsuffixed_int_literals { - ($($name:ident => $kind:ident,)*) => ($( - /// Creates a new unsuffixed integer literal with the specified value. - /// - /// This function will create an integer like `1` where the integer - /// value specified is the first part of the token. No suffix is - /// specified on this token, meaning that invocations like - /// `Literal::i8_unsuffixed(1)` are equivalent to - /// `Literal::u32_unsuffixed(1)`. - /// Literals created from negative numbers might not survive rountrips through - /// `TokenStream` or strings and may be broken into two tokens (`-` and positive literal). - /// - /// Literals created through this method have the `Span::call_site()` - /// span by default, which can be configured with the `set_span` method - /// below. - pub fn $name(n: $kind) -> Literal { - Literal(bridge::client::Literal::integer(&n.to_string())) - } - )*) -} - -impl Literal { - suffixed_int_literals! { - u8_suffixed => u8, - u16_suffixed => u16, - u32_suffixed => u32, - u64_suffixed => u64, - u128_suffixed => u128, - usize_suffixed => usize, - i8_suffixed => i8, - i16_suffixed => i16, - i32_suffixed => i32, - i64_suffixed => i64, - i128_suffixed => i128, - isize_suffixed => isize, - } - - unsuffixed_int_literals! { - u8_unsuffixed => u8, - u16_unsuffixed => u16, - u32_unsuffixed => u32, - u64_unsuffixed => u64, - u128_unsuffixed => u128, - usize_unsuffixed => usize, - i8_unsuffixed => i8, - i16_unsuffixed => i16, - i32_unsuffixed => i32, - i64_unsuffixed => i64, - i128_unsuffixed => i128, - isize_unsuffixed => isize, - } - - /// Creates a new unsuffixed floating-point literal. - /// - /// This constructor is similar to those like `Literal::i8_unsuffixed` where - /// the float's value is emitted directly into the token but no suffix is - /// used, so it may be inferred to be a `f64` later in the compiler. - /// Literals created from negative numbers might not survive rountrips through - /// `TokenStream` or strings and may be broken into two tokens (`-` and positive literal). - /// - /// # Panics - /// - /// This function requires that the specified float is finite, for - /// example if it is infinity or NaN this function will panic. - pub fn f32_unsuffixed(n: f32) -> Literal { - if !n.is_finite() { - panic!("Invalid float literal {n}"); - } - let mut repr = n.to_string(); - if !repr.contains('.') { - repr.push_str(".0"); - } - Literal(bridge::client::Literal::float(&repr)) - } - - /// Creates a new suffixed floating-point literal. - /// - /// This constructor will create a literal like `1.0f32` where the value - /// specified is the preceding part of the token and `f32` is the suffix of - /// the token. This token will always be inferred to be an `f32` in the - /// compiler. - /// Literals created from negative numbers might not survive rountrips through - /// `TokenStream` or strings and may be broken into two tokens (`-` and positive literal). - /// - /// # Panics - /// - /// This function requires that the specified float is finite, for - /// example if it is infinity or NaN this function will panic. - pub fn f32_suffixed(n: f32) -> Literal { - if !n.is_finite() { - panic!("Invalid float literal {n}"); - } - Literal(bridge::client::Literal::f32(&n.to_string())) - } - - /// Creates a new unsuffixed floating-point literal. - /// - /// This constructor is similar to those like `Literal::i8_unsuffixed` where - /// the float's value is emitted directly into the token but no suffix is - /// used, so it may be inferred to be a `f64` later in the compiler. - /// Literals created from negative numbers might not survive rountrips through - /// `TokenStream` or strings and may be broken into two tokens (`-` and positive literal). - /// - /// # Panics - /// - /// This function requires that the specified float is finite, for - /// example if it is infinity or NaN this function will panic. - pub fn f64_unsuffixed(n: f64) -> Literal { - if !n.is_finite() { - panic!("Invalid float literal {n}"); - } - let mut repr = n.to_string(); - if !repr.contains('.') { - repr.push_str(".0"); - } - Literal(bridge::client::Literal::float(&repr)) - } - - /// Creates a new suffixed floating-point literal. - /// - /// This constructor will create a literal like `1.0f64` where the value - /// specified is the preceding part of the token and `f64` is the suffix of - /// the token. This token will always be inferred to be an `f64` in the - /// compiler. - /// Literals created from negative numbers might not survive rountrips through - /// `TokenStream` or strings and may be broken into two tokens (`-` and positive literal). - /// - /// # Panics - /// - /// This function requires that the specified float is finite, for - /// example if it is infinity or NaN this function will panic. - pub fn f64_suffixed(n: f64) -> Literal { - if !n.is_finite() { - panic!("Invalid float literal {n}"); - } - Literal(bridge::client::Literal::f64(&n.to_string())) - } - - /// String literal. - pub fn string(string: &str) -> Literal { - Literal(bridge::client::Literal::string(string)) - } - - /// Character literal. - pub fn character(ch: char) -> Literal { - Literal(bridge::client::Literal::character(ch)) - } - - /// Byte string literal. - pub fn byte_string(bytes: &[u8]) -> Literal { - Literal(bridge::client::Literal::byte_string(bytes)) - } - - /// Returns the span encompassing this literal. - pub fn span(&self) -> Span { - Span(self.0.span()) - } - - /// Configures the span associated for this literal. - pub fn set_span(&mut self, span: Span) { - self.0.set_span(span.0); - } - - /// Returns a `Span` that is a subset of `self.span()` containing only the - /// source bytes in range `range`. Returns `None` if the would-be trimmed - /// span is outside the bounds of `self`. - // FIXME(SergioBenitez): check that the byte range starts and ends at a - // UTF-8 boundary of the source. otherwise, it's likely that a panic will - // occur elsewhere when the source text is printed. - // FIXME(SergioBenitez): there is no way for the user to know what - // `self.span()` actually maps to, so this method can currently only be - // called blindly. For example, `to_string()` for the character 'c' returns - // "'\u{63}'"; there is no way for the user to know whether the source text - // was 'c' or whether it was '\u{63}'. - pub fn subspan<R: RangeBounds<usize>>(&self, range: R) -> Option<Span> { - self.0.subspan(range.start_bound().cloned(), range.end_bound().cloned()).map(Span) - } -} - -/// Parse a single literal from its stringified representation. -/// -/// In order to parse successfully, the input string must not contain anything -/// but the literal token. Specifically, it must not contain whitespace or -/// comments in addition to the literal. -/// -/// The resulting literal token will have a `Span::call_site()` span. -/// -/// NOTE: some errors may cause panics instead of returning `LexError`. We -/// reserve the right to change these errors into `LexError`s later. -impl FromStr for Literal { - type Err = LexError; - - fn from_str(src: &str) -> Result<Self, LexError> { - match bridge::client::Literal::from_str(src) { - Ok(literal) => Ok(Literal(literal)), - Err(()) => Err(LexError), - } - } -} - -/// Prints the literal as a string that should be losslessly convertible -/// back into the same literal (except for possible rounding for floating point literals). -impl fmt::Display for Literal { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.to_string()) - } -} - -impl fmt::Debug for Literal { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -/// Tracked access to environment variables. -pub mod tracked_env { - use std::env::{self, VarError}; - use std::ffi::OsStr; - - /// Retrieve an environment variable and add it to build dependency info. - /// Build system executing the compiler will know that the variable was accessed during - /// compilation, and will be able to rerun the build when the value of that variable changes. - /// Besides the dependency tracking this function should be equivalent to `env::var` from the - /// standard library, except that the argument must be UTF-8. - pub fn var<K: AsRef<OsStr> + AsRef<str>>(key: K) -> Result<String, VarError> { - let key: &str = key.as_ref(); - let value = env::var(key); - super::bridge::client::FreeFunctions::track_env_var(key, value.as_deref().ok()); - value - } -} - -/// Tracked access to additional files. -pub mod tracked_path { - - /// Track a file explicitly. - /// - /// Commonly used for tracking asset preprocessing. - pub fn path<P: AsRef<str>>(path: P) { - let path: &str = path.as_ref(); - super::bridge::client::FreeFunctions::track_path(path); - } -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/quote.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/quote.rs deleted file mode 100644 index b539ab9c0..000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/proc_macro/quote.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! # Quasiquoter -//! This file contains the implementation internals of the quasiquoter provided by `quote!`. - -//! This quasiquoter uses macros 2.0 hygiene to reliably access -//! items from `proc_macro`, to build a `proc_macro::TokenStream`. - -use super::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; - -macro_rules! quote_tt { - (($($t:tt)*)) => { Group::new(Delimiter::Parenthesis, quote!($($t)*)) }; - ([$($t:tt)*]) => { Group::new(Delimiter::Bracket, quote!($($t)*)) }; - ({$($t:tt)*}) => { Group::new(Delimiter::Brace, quote!($($t)*)) }; - (,) => { Punct::new(',', Spacing::Alone) }; - (.) => { Punct::new('.', Spacing::Alone) }; - (:) => { Punct::new(':', Spacing::Alone) }; - (;) => { Punct::new(';', Spacing::Alone) }; - (!) => { Punct::new('!', Spacing::Alone) }; - (<) => { Punct::new('<', Spacing::Alone) }; - (>) => { Punct::new('>', Spacing::Alone) }; - (&) => { Punct::new('&', Spacing::Alone) }; - (=) => { Punct::new('=', Spacing::Alone) }; - ($i:ident) => { Ident::new(stringify!($i), Span::def_site()) }; -} - -macro_rules! quote_ts { - ((@ $($t:tt)*)) => { $($t)* }; - (::) => { - [ - TokenTree::from(Punct::new(':', Spacing::Joint)), - TokenTree::from(Punct::new(':', Spacing::Alone)), - ].iter() - .cloned() - .map(|mut x| { - x.set_span(Span::def_site()); - x - }) - .collect::<TokenStream>() - }; - ($t:tt) => { TokenTree::from(quote_tt!($t)) }; -} - -/// Simpler version of the real `quote!` macro, implemented solely -/// through `macro_rules`, for bootstrapping the real implementation -/// (see the `quote` function), which does not have access to the -/// real `quote!` macro due to the `proc_macro` crate not being -/// able to depend on itself. -/// -/// Note: supported tokens are a subset of the real `quote!`, but -/// unquoting is different: instead of `$x`, this uses `(@ expr)`. -macro_rules! quote { - () => { TokenStream::new() }; - ($($t:tt)*) => { - [ - $(TokenStream::from(quote_ts!($t)),)* - ].iter().cloned().collect::<TokenStream>() - }; -} - -/// Quote a `TokenStream` into a `TokenStream`. -/// This is the actual implementation of the `quote!()` proc macro. -/// -/// It is loaded by the compiler in `register_builtin_macros`. -pub fn quote(stream: TokenStream) -> TokenStream { - if stream.is_empty() { - return quote!(crate::TokenStream::new()); - } - let proc_macro_crate = quote!(crate); - let mut after_dollar = false; - let tokens = stream - .into_iter() - .filter_map(|tree| { - if after_dollar { - after_dollar = false; - match tree { - TokenTree::Ident(_) => { - return Some(quote!(Into::<crate::TokenStream>::into( - Clone::clone(&(@ tree))),)); - } - TokenTree::Punct(ref tt) if tt.as_char() == '$' => {} - _ => panic!("`$` must be followed by an ident or `$` in `quote!`"), - } - } else if let TokenTree::Punct(ref tt) = tree { - if tt.as_char() == '$' { - after_dollar = true; - return None; - } - } - - Some(quote!(crate::TokenStream::from((@ match tree { - TokenTree::Punct(tt) => quote!(crate::TokenTree::Punct(crate::Punct::new( - (@ TokenTree::from(Literal::character(tt.as_char()))), - (@ match tt.spacing() { - Spacing::Alone => quote!(crate::Spacing::Alone), - Spacing::Joint => quote!(crate::Spacing::Joint), - }), - ))), - TokenTree::Group(tt) => quote!(crate::TokenTree::Group(crate::Group::new( - (@ match tt.delimiter() { - Delimiter::Parenthesis => quote!(crate::Delimiter::Parenthesis), - Delimiter::Brace => quote!(crate::Delimiter::Brace), - Delimiter::Bracket => quote!(crate::Delimiter::Bracket), - Delimiter::None => quote!(crate::Delimiter::None), - }), - (@ quote(tt.stream())), - ))), - TokenTree::Ident(tt) => quote!(crate::TokenTree::Ident(crate::Ident::new( - (@ TokenTree::from(Literal::string(&tt.to_string()))), - (@ quote_span(proc_macro_crate.clone(), tt.span())), - ))), - TokenTree::Literal(tt) => quote!(crate::TokenTree::Literal({ - let mut iter = (@ TokenTree::from(Literal::string(&tt.to_string()))) - .parse::<crate::TokenStream>() - .unwrap() - .into_iter(); - if let (Some(crate::TokenTree::Literal(mut lit)), None) = - (iter.next(), iter.next()) - { - lit.set_span((@ quote_span(proc_macro_crate.clone(), tt.span()))); - lit - } else { - unreachable!() - } - })) - })),)) - }) - .collect::<TokenStream>(); - - if after_dollar { - panic!("unexpected trailing `$` in `quote!`"); - } - - quote!([(@ tokens)].iter().cloned().collect::<crate::TokenStream>()) -} - -/// Quote a `Span` into a `TokenStream`. -/// This is needed to implement a custom quoter. -pub fn quote_span(proc_macro_crate: TokenStream, span: Span) -> TokenStream { - let id = span.save_span(); - quote!((@ proc_macro_crate ) ::Span::recover_proc_macro_span((@ TokenTree::from(Literal::usize_unsuffixed(id))))) -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/ra_server.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/ra_server.rs deleted file mode 100644 index 22d4ad94f..000000000 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_58/ra_server.rs +++ /dev/null @@ -1,822 +0,0 @@ -//! Rustc proc-macro server implementation with tt -//! -//! Based on idea from <https://github.com/fedochet/rust-proc-macro-expander> -//! The lib-proc-macro server backend is `TokenStream`-agnostic, such that -//! we could provide any TokenStream implementation. -//! The original idea from fedochet is using proc-macro2 as backend, -//! we use tt instead for better integration with RA. -//! -//! FIXME: No span and source file information is implemented yet - -use super::proc_macro::bridge::{self, server}; - -use std::collections::HashMap; -use std::hash::Hash; -use std::ops::Bound; -use std::{ascii, vec::IntoIter}; - -type Group = tt::Subtree; -type TokenTree = tt::TokenTree; -type Punct = tt::Punct; -type Spacing = tt::Spacing; -type Literal = tt::Literal; -type Span = tt::TokenId; - -#[derive(Debug, Clone)] -pub struct TokenStream { - pub token_trees: Vec<TokenTree>, -} - -impl TokenStream { - pub fn new() -> Self { - TokenStream { token_trees: Default::default() } - } - - pub fn with_subtree(subtree: tt::Subtree) -> Self { - if subtree.delimiter.is_some() { - TokenStream { token_trees: vec![TokenTree::Subtree(subtree)] } - } else { - TokenStream { token_trees: subtree.token_trees } - } - } - - pub fn into_subtree(self) -> tt::Subtree { - tt::Subtree { delimiter: None, token_trees: self.token_trees } - } - - pub fn is_empty(&self) -> bool { - self.token_trees.is_empty() - } -} - -/// Creates a token stream containing a single token tree. -impl From<TokenTree> for TokenStream { - fn from(tree: TokenTree) -> TokenStream { - TokenStream { token_trees: vec![tree] } - } -} - -/// Collects a number of token trees into a single stream. -impl FromIterator<TokenTree> for TokenStream { - fn from_iter<I: IntoIterator<Item = TokenTree>>(trees: I) -> Self { - trees.into_iter().map(TokenStream::from).collect() - } -} - -/// A "flattening" operation on token streams, collects token trees -/// from multiple token streams into a single stream. -impl FromIterator<TokenStream> for TokenStream { - fn from_iter<I: IntoIterator<Item = TokenStream>>(streams: I) -> Self { - let mut builder = TokenStreamBuilder::new(); - streams.into_iter().for_each(|stream| builder.push(stream)); - builder.build() - } -} - -impl Extend<TokenTree> for TokenStream { - fn extend<I: IntoIterator<Item = TokenTree>>(&mut self, trees: I) { - self.extend(trees.into_iter().map(TokenStream::from)); - } -} - -impl Extend<TokenStream> for TokenStream { - fn extend<I: IntoIterator<Item = TokenStream>>(&mut self, streams: I) { - for item in streams { - for tkn in item { - match tkn { - tt::TokenTree::Subtree(subtree) if subtree.delimiter.is_none() => { - self.token_trees.extend(subtree.token_trees); - } - _ => { - self.token_trees.push(tkn); - } - } - } - } - } -} - -#[derive(Clone)] -pub struct SourceFile { - // FIXME stub -} - -type Level = super::proc_macro::Level; -type LineColumn = super::proc_macro::LineColumn; - -/// A structure representing a diagnostic message and associated children -/// messages. -#[derive(Clone, Debug)] -pub struct Diagnostic { - level: Level, - message: String, - spans: Vec<Span>, - children: Vec<Diagnostic>, -} - -impl Diagnostic { - /// Creates a new diagnostic with the given `level` and `message`. - pub fn new<T: Into<String>>(level: Level, message: T) -> Diagnostic { - Diagnostic { level, message: message.into(), spans: vec![], children: vec![] } - } -} - -// Rustc Server Ident has to be `Copyable` -// We use a stub here for bypassing -#[derive(Hash, Eq, PartialEq, Copy, Clone)] -pub struct IdentId(u32); - -#[derive(Clone, Hash, Eq, PartialEq)] -struct IdentData(tt::Ident); - -#[derive(Default)] -struct IdentInterner { - idents: HashMap<IdentData, u32>, - ident_data: Vec<IdentData>, -} - -impl IdentInterner { - fn intern(&mut self, data: &IdentData) -> u32 { - if let Some(index) = self.idents.get(data) { - return *index; - } - - let index = self.idents.len() as u32; - self.ident_data.push(data.clone()); - self.idents.insert(data.clone(), index); - index - } - - fn get(&self, index: u32) -> &IdentData { - &self.ident_data[index as usize] - } - - #[allow(unused)] - fn get_mut(&mut self, index: u32) -> &mut IdentData { - self.ident_data.get_mut(index as usize).expect("Should be consistent") - } -} - -pub struct TokenStreamBuilder { - acc: TokenStream, -} - -/// Public implementation details for the `TokenStream` type, such as iterators. -pub mod token_stream { - use std::str::FromStr; - - use super::{TokenStream, TokenTree}; - - /// An iterator over `TokenStream`'s `TokenTree`s. - /// The iteration is "shallow", e.g., the iterator doesn't recurse into delimited groups, - /// and returns whole groups as token trees. - impl IntoIterator for TokenStream { - type Item = TokenTree; - type IntoIter = super::IntoIter<TokenTree>; - - fn into_iter(self) -> Self::IntoIter { - self.token_trees.into_iter() - } - } - - type LexError = String; - - /// Attempts to break the string into tokens and parse those tokens into a token stream. - /// May fail for a number of reasons, for example, if the string contains unbalanced delimiters - /// or characters not existing in the language. - /// All tokens in the parsed stream get `Span::call_site()` spans. - /// - /// NOTE: some errors may cause panics instead of returning `LexError`. We reserve the right to - /// change these errors into `LexError`s later. - impl FromStr for TokenStream { - type Err = LexError; - - fn from_str(src: &str) -> Result<TokenStream, LexError> { - let (subtree, _token_map) = - mbe::parse_to_token_tree(src).ok_or("Failed to parse from mbe")?; - - let subtree = subtree_replace_token_ids_with_unspecified(subtree); - Ok(TokenStream::with_subtree(subtree)) - } - } - - impl ToString for TokenStream { - fn to_string(&self) -> String { - tt::pretty(&self.token_trees) - } - } - - fn subtree_replace_token_ids_with_unspecified(subtree: tt::Subtree) -> tt::Subtree { - tt::Subtree { - delimiter: subtree - .delimiter - .map(|d| tt::Delimiter { id: tt::TokenId::unspecified(), ..d }), - token_trees: subtree - .token_trees - .into_iter() - .map(token_tree_replace_token_ids_with_unspecified) - .collect(), - } - } - - fn token_tree_replace_token_ids_with_unspecified(tt: tt::TokenTree) -> tt::TokenTree { - match tt { - tt::TokenTree::Leaf(leaf) => { - tt::TokenTree::Leaf(leaf_replace_token_ids_with_unspecified(leaf)) - } - tt::TokenTree::Subtree(subtree) => { - tt::TokenTree::Subtree(subtree_replace_token_ids_with_unspecified(subtree)) - } - } - } - - fn leaf_replace_token_ids_with_unspecified(leaf: tt::Leaf) -> tt::Leaf { - match leaf { - tt::Leaf::Literal(lit) => { - tt::Leaf::Literal(tt::Literal { id: tt::TokenId::unspecified(), ..lit }) - } - tt::Leaf::Punct(punct) => { - tt::Leaf::Punct(tt::Punct { id: tt::TokenId::unspecified(), ..punct }) - } - tt::Leaf::Ident(ident) => { - tt::Leaf::Ident(tt::Ident { id: tt::TokenId::unspecified(), ..ident }) - } - } - } -} - -impl TokenStreamBuilder { - fn new() -> TokenStreamBuilder { - TokenStreamBuilder { acc: TokenStream::new() } - } - - fn push(&mut self, stream: TokenStream) { - self.acc.extend(stream.into_iter()) - } - - fn build(self) -> TokenStream { - self.acc - } -} - -pub struct FreeFunctions; - -#[derive(Clone)] -pub struct TokenStreamIter { - trees: IntoIter<TokenTree>, -} - -#[derive(Default)] -pub struct RustAnalyzer { - ident_interner: IdentInterner, - // FIXME: store span information here. -} - -impl server::Types for RustAnalyzer { - type FreeFunctions = FreeFunctions; - type TokenStream = TokenStream; - type TokenStreamBuilder = TokenStreamBuilder; - type TokenStreamIter = TokenStreamIter; - type Group = Group; - type Punct = Punct; - type Ident = IdentId; - type Literal = Literal; - type SourceFile = SourceFile; - type Diagnostic = Diagnostic; - type Span = Span; - type MultiSpan = Vec<Span>; -} - -impl server::FreeFunctions for RustAnalyzer { - fn track_env_var(&mut self, _var: &str, _value: Option<&str>) { - // FIXME: track env var accesses - // https://github.com/rust-lang/rust/pull/71858 - } - fn track_path(&mut self, _path: &str) {} -} - -impl server::TokenStream for RustAnalyzer { - fn new(&mut self) -> Self::TokenStream { - Self::TokenStream::new() - } - - fn is_empty(&mut self, stream: &Self::TokenStream) -> bool { - stream.is_empty() - } - fn from_str(&mut self, src: &str) -> Self::TokenStream { - use std::str::FromStr; - - Self::TokenStream::from_str(src).expect("cannot parse string") - } - fn to_string(&mut self, stream: &Self::TokenStream) -> String { - stream.to_string() - } - fn from_token_tree( - &mut self, - tree: bridge::TokenTree<Self::Group, Self::Punct, Self::Ident, Self::Literal>, - ) -> Self::TokenStream { - match tree { - bridge::TokenTree::Group(group) => { - let tree = TokenTree::from(group); - Self::TokenStream::from_iter(vec![tree]) - } - - bridge::TokenTree::Ident(IdentId(index)) => { - let IdentData(ident) = self.ident_interner.get(index).clone(); - let ident: tt::Ident = ident; - let leaf = tt::Leaf::from(ident); - let tree = TokenTree::from(leaf); - Self::TokenStream::from_iter(vec![tree]) - } - - bridge::TokenTree::Literal(literal) => { - let leaf = tt::Leaf::from(literal); - let tree = TokenTree::from(leaf); - Self::TokenStream::from_iter(vec![tree]) - } - - bridge::TokenTree::Punct(p) => { - let leaf = tt::Leaf::from(p); - let tree = TokenTree::from(leaf); - Self::TokenStream::from_iter(vec![tree]) - } - } - } - - fn into_iter(&mut self, stream: Self::TokenStream) -> Self::TokenStreamIter { - let trees: Vec<TokenTree> = stream.into_iter().collect(); - TokenStreamIter { trees: trees.into_iter() } - } - - fn expand_expr(&mut self, self_: &Self::TokenStream) -> Result<Self::TokenStream, ()> { - Ok(self_.clone()) - } -} - -impl server::TokenStreamBuilder for RustAnalyzer { - fn new(&mut self) -> Self::TokenStreamBuilder { - Self::TokenStreamBuilder::new() - } - fn push(&mut self, builder: &mut Self::TokenStreamBuilder, stream: Self::TokenStream) { - builder.push(stream) - } - fn build(&mut self, builder: Self::TokenStreamBuilder) -> Self::TokenStream { - builder.build() - } -} - -impl server::TokenStreamIter for RustAnalyzer { - fn next( - &mut self, - iter: &mut Self::TokenStreamIter, - ) -> Option<bridge::TokenTree<Self::Group, Self::Punct, Self::Ident, Self::Literal>> { - iter.trees.next().map(|tree| match tree { - TokenTree::Subtree(group) => bridge::TokenTree::Group(group), - TokenTree::Leaf(tt::Leaf::Ident(ident)) => { - bridge::TokenTree::Ident(IdentId(self.ident_interner.intern(&IdentData(ident)))) - } - TokenTree::Leaf(tt::Leaf::Literal(literal)) => bridge::TokenTree::Literal(literal), - TokenTree::Leaf(tt::Leaf::Punct(punct)) => bridge::TokenTree::Punct(punct), - }) - } -} - -fn delim_to_internal(d: bridge::Delimiter) -> Option<tt::Delimiter> { - let kind = match d { - bridge::Delimiter::Parenthesis => tt::DelimiterKind::Parenthesis, - bridge::Delimiter::Brace => tt::DelimiterKind::Brace, - bridge::Delimiter::Bracket => tt::DelimiterKind::Bracket, - bridge::Delimiter::None => return None, - }; - Some(tt::Delimiter { id: tt::TokenId::unspecified(), kind }) -} - -fn delim_to_external(d: Option<tt::Delimiter>) -> bridge::Delimiter { - match d.map(|it| it.kind) { - Some(tt::DelimiterKind::Parenthesis) => bridge::Delimiter::Parenthesis, - Some(tt::DelimiterKind::Brace) => bridge::Delimiter::Brace, - Some(tt::DelimiterKind::Bracket) => bridge::Delimiter::Bracket, - None => bridge::Delimiter::None, - } -} - -fn spacing_to_internal(spacing: bridge::Spacing) -> Spacing { - match spacing { - bridge::Spacing::Alone => Spacing::Alone, - bridge::Spacing::Joint => Spacing::Joint, - } -} - -fn spacing_to_external(spacing: Spacing) -> bridge::Spacing { - match spacing { - Spacing::Alone => bridge::Spacing::Alone, - Spacing::Joint => bridge::Spacing::Joint, - } -} - -impl server::Group for RustAnalyzer { - fn new(&mut self, delimiter: bridge::Delimiter, stream: Self::TokenStream) -> Self::Group { - Self::Group { delimiter: delim_to_internal(delimiter), token_trees: stream.token_trees } - } - fn delimiter(&mut self, group: &Self::Group) -> bridge::Delimiter { - delim_to_external(group.delimiter) - } - - // NOTE: Return value of do not include delimiter - fn stream(&mut self, group: &Self::Group) -> Self::TokenStream { - TokenStream { token_trees: group.token_trees.clone() } - } - - fn span(&mut self, group: &Self::Group) -> Self::Span { - group.delimiter.map(|it| it.id).unwrap_or_else(tt::TokenId::unspecified) - } - - fn set_span(&mut self, group: &mut Self::Group, span: Self::Span) { - if let Some(delim) = &mut group.delimiter { - delim.id = span; - } - } - - fn span_open(&mut self, group: &Self::Group) -> Self::Span { - // FIXME we only store one `TokenId` for the delimiters - group.delimiter.map(|it| it.id).unwrap_or_else(tt::TokenId::unspecified) - } - - fn span_close(&mut self, group: &Self::Group) -> Self::Span { - // FIXME we only store one `TokenId` for the delimiters - group.delimiter.map(|it| it.id).unwrap_or_else(tt::TokenId::unspecified) - } -} - -impl server::Punct for RustAnalyzer { - fn new(&mut self, ch: char, spacing: bridge::Spacing) -> Self::Punct { - tt::Punct { - char: ch, - spacing: spacing_to_internal(spacing), - id: tt::TokenId::unspecified(), - } - } - fn as_char(&mut self, punct: Self::Punct) -> char { - punct.char - } - fn spacing(&mut self, punct: Self::Punct) -> bridge::Spacing { - spacing_to_external(punct.spacing) - } - fn span(&mut self, punct: Self::Punct) -> Self::Span { - punct.id - } - fn with_span(&mut self, punct: Self::Punct, span: Self::Span) -> Self::Punct { - tt::Punct { id: span, ..punct } - } -} - -impl server::Ident for RustAnalyzer { - fn new(&mut self, string: &str, span: Self::Span, is_raw: bool) -> Self::Ident { - IdentId(self.ident_interner.intern(&IdentData(tt::Ident::new_with_is_raw( - string.into(), - span, - is_raw, - )))) - } - - fn span(&mut self, ident: Self::Ident) -> Self::Span { - self.ident_interner.get(ident.0).0.id - } - fn with_span(&mut self, ident: Self::Ident, span: Self::Span) -> Self::Ident { - let data = self.ident_interner.get(ident.0); - let new = IdentData(tt::Ident { id: span, ..data.0.clone() }); - IdentId(self.ident_interner.intern(&new)) - } -} - -impl server::Literal for RustAnalyzer { - fn debug_kind(&mut self, _literal: &Self::Literal) -> String { - // r-a: debug_kind and suffix are unsupported; corresponding client code has been changed to not call these. - // They must still be present to be ABI-compatible and work with upstream proc_macro. - "".to_owned() - } - fn from_str(&mut self, s: &str) -> Result<Self::Literal, ()> { - Ok(Literal { text: s.into(), id: tt::TokenId::unspecified() }) - } - fn symbol(&mut self, literal: &Self::Literal) -> String { - literal.text.to_string() - } - fn suffix(&mut self, _literal: &Self::Literal) -> Option<String> { - None - } - - fn to_string(&mut self, literal: &Self::Literal) -> String { - literal.to_string() - } - - fn integer(&mut self, n: &str) -> Self::Literal { - let n = match n.parse::<i128>() { - Ok(n) => n.to_string(), - Err(_) => n.parse::<u128>().unwrap().to_string(), - }; - Literal { text: n.into(), id: tt::TokenId::unspecified() } - } - - fn typed_integer(&mut self, n: &str, kind: &str) -> Self::Literal { - macro_rules! def_suffixed_integer { - ($kind:ident, $($ty:ty),*) => { - match $kind { - $( - stringify!($ty) => { - let n: $ty = n.parse().unwrap(); - format!(concat!("{}", stringify!($ty)), n) - } - )* - _ => unimplemented!("unknown args for typed_integer: n {}, kind {}", n, $kind), - } - } - } - - let text = def_suffixed_integer! {kind, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize}; - - Literal { text: text.into(), id: tt::TokenId::unspecified() } - } - - fn float(&mut self, n: &str) -> Self::Literal { - let n: f64 = n.parse().unwrap(); - let mut text = f64::to_string(&n); - if !text.contains('.') { - text += ".0" - } - Literal { text: text.into(), id: tt::TokenId::unspecified() } - } - - fn f32(&mut self, n: &str) -> Self::Literal { - let n: f32 = n.parse().unwrap(); - let text = format!("{n}f32"); - Literal { text: text.into(), id: tt::TokenId::unspecified() } - } - - fn f64(&mut self, n: &str) -> Self::Literal { - let n: f64 = n.parse().unwrap(); - let text = format!("{n}f64"); - Literal { text: text.into(), id: tt::TokenId::unspecified() } - } - - fn string(&mut self, string: &str) -> Self::Literal { - let mut escaped = String::new(); - for ch in string.chars() { - escaped.extend(ch.escape_debug()); - } - Literal { text: format!("\"{escaped}\"").into(), id: tt::TokenId::unspecified() } - } - - fn character(&mut self, ch: char) -> Self::Literal { - Literal { text: format!("'{ch}'").into(), id: tt::TokenId::unspecified() } - } - - fn byte_string(&mut self, bytes: &[u8]) -> Self::Literal { - let string = bytes - .iter() - .cloned() - .flat_map(ascii::escape_default) - .map(Into::<char>::into) - .collect::<String>(); - - Literal { text: format!("b\"{string}\"").into(), id: tt::TokenId::unspecified() } - } - - fn span(&mut self, literal: &Self::Literal) -> Self::Span { - literal.id - } - - fn set_span(&mut self, literal: &mut Self::Literal, span: Self::Span) { - literal.id = span; - } - - fn subspan( - &mut self, - _literal: &Self::Literal, - _start: Bound<usize>, - _end: Bound<usize>, - ) -> Option<Self::Span> { - // FIXME handle span - None - } -} - -impl server::SourceFile for RustAnalyzer { - // FIXME these are all stubs - fn eq(&mut self, _file1: &Self::SourceFile, _file2: &Self::SourceFile) -> bool { - true - } - fn path(&mut self, _file: &Self::SourceFile) -> String { - String::new() - } - fn is_real(&mut self, _file: &Self::SourceFile) -> bool { - true - } -} - -impl server::Diagnostic for RustAnalyzer { - fn new(&mut self, level: Level, msg: &str, spans: Self::MultiSpan) -> Self::Diagnostic { - let mut diag = Diagnostic::new(level, msg); - diag.spans = spans; - diag - } - - fn sub( - &mut self, - _diag: &mut Self::Diagnostic, - _level: Level, - _msg: &str, - _spans: Self::MultiSpan, - ) { - // FIXME handle diagnostic - // - } - - fn emit(&mut self, _diag: Self::Diagnostic) { - // FIXME handle diagnostic - // diag.emit() - } -} - -impl server::Span for RustAnalyzer { - fn debug(&mut self, span: Self::Span) -> String { - format!("{:?}", span.0) - } - fn def_site(&mut self) -> Self::Span { - // MySpan(self.span_interner.intern(&MySpanData(Span::def_site()))) - // FIXME handle span - tt::TokenId::unspecified() - } - fn call_site(&mut self) -> Self::Span { - // MySpan(self.span_interner.intern(&MySpanData(Span::call_site()))) - // FIXME handle span - tt::TokenId::unspecified() - } - fn source_file(&mut self, _span: Self::Span) -> Self::SourceFile { - SourceFile {} - } - fn save_span(&mut self, _span: Self::Span) -> usize { - // FIXME stub - 0 - } - fn recover_proc_macro_span(&mut self, _id: usize) -> Self::Span { - // FIXME stub - tt::TokenId::unspecified() - } - /// Recent feature, not yet in the proc_macro - /// - /// See PR: - /// https://github.com/rust-lang/rust/pull/55780 - fn source_text(&mut self, _span: Self::Span) -> Option<String> { - None - } - - fn parent(&mut self, _span: Self::Span) -> Option<Self::Span> { - // FIXME handle span - None - } - fn source(&mut self, span: Self::Span) -> Self::Span { - // FIXME handle span - span - } - fn start(&mut self, _span: Self::Span) -> LineColumn { - // FIXME handle span - LineColumn { line: 0, column: 0 } - } - fn end(&mut self, _span: Self::Span) -> LineColumn { - // FIXME handle span - LineColumn { line: 0, column: 0 } - } - fn join(&mut self, first: Self::Span, _second: Self::Span) -> Option<Self::Span> { - // Just return the first span again, because some macros will unwrap the result. - Some(first) - } - fn resolved_at(&mut self, _span: Self::Span, _at: Self::Span) -> Self::Span { - // FIXME handle span - tt::TokenId::unspecified() - } - - fn mixed_site(&mut self) -> Self::Span { - // FIXME handle span - tt::TokenId::unspecified() - } - - fn after(&mut self, _self_: Self::Span) -> Self::Span { - tt::TokenId::unspecified() - } - - fn before(&mut self, _self_: Self::Span) -> Self::Span { - tt::TokenId::unspecified() - } -} - -impl server::MultiSpan for RustAnalyzer { - fn new(&mut self) -> Self::MultiSpan { - // FIXME handle span - vec![] - } - - fn push(&mut self, other: &mut Self::MultiSpan, span: Self::Span) { - //TODP - other.push(span) - } -} - -#[cfg(test)] -mod tests { - use super::super::proc_macro::bridge::server::Literal; - use super::*; - - #[test] - fn test_ra_server_literals() { - let mut srv = RustAnalyzer { ident_interner: IdentInterner::default() }; - assert_eq!(srv.integer("1234").text, "1234"); - - assert_eq!(srv.typed_integer("12", "u8").text, "12u8"); - assert_eq!(srv.typed_integer("255", "u16").text, "255u16"); - assert_eq!(srv.typed_integer("1234", "u32").text, "1234u32"); - assert_eq!(srv.typed_integer("15846685", "u64").text, "15846685u64"); - assert_eq!(srv.typed_integer("15846685258", "u128").text, "15846685258u128"); - assert_eq!(srv.typed_integer("156788984", "usize").text, "156788984usize"); - assert_eq!(srv.typed_integer("127", "i8").text, "127i8"); - assert_eq!(srv.typed_integer("255", "i16").text, "255i16"); - assert_eq!(srv.typed_integer("1234", "i32").text, "1234i32"); - assert_eq!(srv.typed_integer("15846685", "i64").text, "15846685i64"); - assert_eq!(srv.typed_integer("15846685258", "i128").text, "15846685258i128"); - assert_eq!(srv.float("0").text, "0.0"); - assert_eq!(srv.float("15684.5867").text, "15684.5867"); - assert_eq!(srv.f32("15684.58").text, "15684.58f32"); - assert_eq!(srv.f64("15684.58").text, "15684.58f64"); - - assert_eq!(srv.string("hello_world").text, "\"hello_world\""); - assert_eq!(srv.character('c').text, "'c'"); - assert_eq!(srv.byte_string(b"1234586\x88").text, "b\"1234586\\x88\""); - - // u128::max - assert_eq!( - srv.integer("340282366920938463463374607431768211455").text, - "340282366920938463463374607431768211455" - ); - // i128::min - assert_eq!( - srv.integer("-170141183460469231731687303715884105728").text, - "-170141183460469231731687303715884105728" - ); - } - - #[test] - fn test_ra_server_to_string() { - let s = TokenStream { - token_trees: vec![ - tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { - text: "struct".into(), - id: tt::TokenId::unspecified(), - })), - tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { - text: "T".into(), - id: tt::TokenId::unspecified(), - })), - tt::TokenTree::Subtree(tt::Subtree { - delimiter: Some(tt::Delimiter { - id: tt::TokenId::unspecified(), - kind: tt::DelimiterKind::Brace, - }), - token_trees: vec![], - }), - ], - }; - - assert_eq!(s.to_string(), "struct T {}"); - } - - #[test] - fn test_ra_server_from_str() { - use std::str::FromStr; - let subtree_paren_a = tt::TokenTree::Subtree(tt::Subtree { - delimiter: Some(tt::Delimiter { - id: tt::TokenId::unspecified(), - kind: tt::DelimiterKind::Parenthesis, - }), - token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { - text: "a".into(), - id: tt::TokenId::unspecified(), - }))], - }); - - let t1 = TokenStream::from_str("(a)").unwrap(); - assert_eq!(t1.token_trees.len(), 1); - assert_eq!(t1.token_trees[0], subtree_paren_a); - - let t2 = TokenStream::from_str("(a);").unwrap(); - assert_eq!(t2.token_trees.len(), 2); - assert_eq!(t2.token_trees[0], subtree_paren_a); - - let underscore = TokenStream::from_str("_").unwrap(); - assert_eq!( - underscore.token_trees[0], - tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { - text: "_".into(), - id: tt::TokenId::unspecified(), - })) - ); - } -} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_63/mod.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_63/mod.rs index 76e89e319..93805c893 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_63/mod.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_63/mod.rs @@ -11,6 +11,7 @@ mod ra_server; use libloading::Library; use proc_macro_api::ProcMacroKind; +use super::tt; use super::PanicMessage; pub use ra_server::TokenStream; diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_63/proc_macro/mod.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_63/proc_macro/mod.rs index 7ab1f421d..89bd10da5 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_63/proc_macro/mod.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_63/proc_macro/mod.rs @@ -124,8 +124,8 @@ impl FromStr for TokenStream { /// into the same token stream (modulo spans), except for possibly `TokenTree::Group`s /// with `Delimiter::None` delimiters and negative numeric literals. impl fmt::Display for TokenStream { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.to_string()) + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + unimplemented!() } } @@ -572,8 +572,8 @@ impl From<Literal> for TokenTree { /// into the same token tree (modulo spans), except for possibly `TokenTree::Group`s /// with `Delimiter::None` delimiters and negative numeric literals. impl fmt::Display for TokenTree { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.to_string()) + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + unimplemented!() } } @@ -669,8 +669,8 @@ impl Group { /// into the same group (modulo spans), except for possibly `TokenTree::Group`s /// with `Delimiter::None` delimiters. impl fmt::Display for Group { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.to_string()) + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + unimplemented!() } } @@ -743,8 +743,8 @@ impl Punct { /// Prints the punctuation character as a string that should be losslessly convertible /// back into the same character. impl fmt::Display for Punct { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.to_string()) + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + unimplemented!() } } @@ -821,8 +821,8 @@ impl Ident { /// Prints the identifier as a string that should be losslessly convertible /// back into the same identifier. impl fmt::Display for Ident { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.to_string()) + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + unimplemented!() } } @@ -1064,8 +1064,8 @@ impl FromStr for Literal { /// Prints the literal as a string that should be losslessly convertible /// back into the same literal (except for possible rounding for floating point literals). impl fmt::Display for Literal { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.to_string()) + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + unimplemented!() } } diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_63/ra_server.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_63/ra_server.rs index f82f20c37..30baf3a13 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_63/ra_server.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_1_63/ra_server.rs @@ -15,6 +15,8 @@ use std::hash::Hash; use std::ops::Bound; use std::{ascii, vec::IntoIter}; +use crate::tt; + type Group = tt::Subtree; type TokenTree = tt::TokenTree; type Punct = tt::Punct; @@ -33,7 +35,7 @@ impl TokenStream { } pub fn with_subtree(subtree: tt::Subtree) -> Self { - if subtree.delimiter.is_some() { + if subtree.delimiter.kind != tt::DelimiterKind::Invisible { TokenStream { token_trees: vec![TokenTree::Subtree(subtree)] } } else { TokenStream { token_trees: subtree.token_trees } @@ -41,7 +43,7 @@ impl TokenStream { } pub fn into_subtree(self) -> tt::Subtree { - tt::Subtree { delimiter: None, token_trees: self.token_trees } + tt::Subtree { delimiter: tt::Delimiter::UNSPECIFIED, token_trees: self.token_trees } } pub fn is_empty(&self) -> bool { @@ -84,7 +86,9 @@ impl Extend<TokenStream> for TokenStream { for item in streams { for tkn in item { match tkn { - tt::TokenTree::Subtree(subtree) if subtree.delimiter.is_none() => { + tt::TokenTree::Subtree(subtree) + if subtree.delimiter.kind == tt::DelimiterKind::Invisible => + { self.token_trees.extend(subtree.token_trees); } _ => { @@ -165,7 +169,7 @@ pub struct TokenStreamBuilder { pub mod token_stream { use std::str::FromStr; - use super::{TokenStream, TokenTree}; + use super::{tt, TokenStream, TokenTree}; /// An iterator over `TokenStream`'s `TokenTree`s. /// The iteration is "shallow", e.g., the iterator doesn't recurse into delimited groups, @@ -202,15 +206,17 @@ pub mod token_stream { impl ToString for TokenStream { fn to_string(&self) -> String { - tt::pretty(&self.token_trees) + ::tt::pretty(&self.token_trees) } } fn subtree_replace_token_ids_with_unspecified(subtree: tt::Subtree) -> tt::Subtree { tt::Subtree { - delimiter: subtree - .delimiter - .map(|d| tt::Delimiter { id: tt::TokenId::unspecified(), ..d }), + delimiter: tt::Delimiter { + open: tt::TokenId::UNSPECIFIED, + close: tt::TokenId::UNSPECIFIED, + ..subtree.delimiter + }, token_trees: subtree .token_trees .into_iter() @@ -233,13 +239,13 @@ pub mod token_stream { fn leaf_replace_token_ids_with_unspecified(leaf: tt::Leaf) -> tt::Leaf { match leaf { tt::Leaf::Literal(lit) => { - tt::Leaf::Literal(tt::Literal { id: tt::TokenId::unspecified(), ..lit }) + tt::Leaf::Literal(tt::Literal { span: tt::TokenId::unspecified(), ..lit }) } tt::Leaf::Punct(punct) => { - tt::Leaf::Punct(tt::Punct { id: tt::TokenId::unspecified(), ..punct }) + tt::Leaf::Punct(tt::Punct { span: tt::TokenId::unspecified(), ..punct }) } tt::Leaf::Ident(ident) => { - tt::Leaf::Ident(tt::Ident { id: tt::TokenId::unspecified(), ..ident }) + tt::Leaf::Ident(tt::Ident { span: tt::TokenId::unspecified(), ..ident }) } } } @@ -389,22 +395,22 @@ impl server::TokenStream for RustAnalyzer { } } -fn delim_to_internal(d: bridge::Delimiter) -> Option<tt::Delimiter> { +fn delim_to_internal(d: bridge::Delimiter) -> tt::Delimiter { let kind = match d { bridge::Delimiter::Parenthesis => tt::DelimiterKind::Parenthesis, bridge::Delimiter::Brace => tt::DelimiterKind::Brace, bridge::Delimiter::Bracket => tt::DelimiterKind::Bracket, - bridge::Delimiter::None => return None, + bridge::Delimiter::None => tt::DelimiterKind::Invisible, }; - Some(tt::Delimiter { id: tt::TokenId::unspecified(), kind }) + tt::Delimiter { open: tt::TokenId::unspecified(), close: tt::TokenId::unspecified(), kind } } -fn delim_to_external(d: Option<tt::Delimiter>) -> bridge::Delimiter { - match d.map(|it| it.kind) { - Some(tt::DelimiterKind::Parenthesis) => bridge::Delimiter::Parenthesis, - Some(tt::DelimiterKind::Brace) => bridge::Delimiter::Brace, - Some(tt::DelimiterKind::Bracket) => bridge::Delimiter::Bracket, - None => bridge::Delimiter::None, +fn delim_to_external(d: tt::Delimiter) -> bridge::Delimiter { + match d.kind { + tt::DelimiterKind::Parenthesis => bridge::Delimiter::Parenthesis, + tt::DelimiterKind::Brace => bridge::Delimiter::Brace, + tt::DelimiterKind::Bracket => bridge::Delimiter::Bracket, + tt::DelimiterKind::Invisible => bridge::Delimiter::None, } } @@ -443,23 +449,19 @@ impl server::Group for RustAnalyzer { } fn span(&mut self, group: &Self::Group) -> Self::Span { - group.delimiter.map(|it| it.id).unwrap_or_else(tt::TokenId::unspecified) + group.delimiter.open } fn set_span(&mut self, group: &mut Self::Group, span: Self::Span) { - if let Some(delim) = &mut group.delimiter { - delim.id = span; - } + group.delimiter.open = span; } fn span_open(&mut self, group: &Self::Group) -> Self::Span { - // FIXME we only store one `TokenId` for the delimiters - group.delimiter.map(|it| it.id).unwrap_or_else(tt::TokenId::unspecified) + group.delimiter.open } fn span_close(&mut self, group: &Self::Group) -> Self::Span { - // FIXME we only store one `TokenId` for the delimiters - group.delimiter.map(|it| it.id).unwrap_or_else(tt::TokenId::unspecified) + group.delimiter.close } } @@ -468,7 +470,7 @@ impl server::Punct for RustAnalyzer { tt::Punct { char: ch, spacing: spacing_to_internal(spacing), - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), } } fn as_char(&mut self, punct: Self::Punct) -> char { @@ -478,28 +480,27 @@ impl server::Punct for RustAnalyzer { spacing_to_external(punct.spacing) } fn span(&mut self, punct: Self::Punct) -> Self::Span { - punct.id + punct.span } fn with_span(&mut self, punct: Self::Punct, span: Self::Span) -> Self::Punct { - tt::Punct { id: span, ..punct } + tt::Punct { span: span, ..punct } } } impl server::Ident for RustAnalyzer { fn new(&mut self, string: &str, span: Self::Span, is_raw: bool) -> Self::Ident { - IdentId(self.ident_interner.intern(&IdentData(tt::Ident::new_with_is_raw( - string.into(), + IdentId(self.ident_interner.intern(&IdentData(tt::Ident { + text: if is_raw { ::tt::SmolStr::from_iter(["r#", string]) } else { string.into() }, span, - is_raw, - )))) + }))) } fn span(&mut self, ident: Self::Ident) -> Self::Span { - self.ident_interner.get(ident.0).0.id + self.ident_interner.get(ident.0).0.span } fn with_span(&mut self, ident: Self::Ident, span: Self::Span) -> Self::Ident { let data = self.ident_interner.get(ident.0); - let new = IdentData(tt::Ident { id: span, ..data.0.clone() }); + let new = IdentData(tt::Ident { span: span, ..data.0.clone() }); IdentId(self.ident_interner.intern(&new)) } } @@ -511,7 +512,7 @@ impl server::Literal for RustAnalyzer { "".to_owned() } fn from_str(&mut self, s: &str) -> Result<Self::Literal, ()> { - Ok(Literal { text: s.into(), id: tt::TokenId::unspecified() }) + Ok(Literal { text: s.into(), span: tt::TokenId::unspecified() }) } fn symbol(&mut self, literal: &Self::Literal) -> String { literal.text.to_string() @@ -529,7 +530,7 @@ impl server::Literal for RustAnalyzer { Ok(n) => n.to_string(), Err(_) => n.parse::<u128>().unwrap().to_string(), }; - Literal { text: n.into(), id: tt::TokenId::unspecified() } + Literal { text: n.into(), span: tt::TokenId::unspecified() } } fn typed_integer(&mut self, n: &str, kind: &str) -> Self::Literal { @@ -549,7 +550,7 @@ impl server::Literal for RustAnalyzer { let text = def_suffixed_integer! {kind, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize}; - Literal { text: text.into(), id: tt::TokenId::unspecified() } + Literal { text: text.into(), span: tt::TokenId::unspecified() } } fn float(&mut self, n: &str) -> Self::Literal { @@ -558,19 +559,19 @@ impl server::Literal for RustAnalyzer { if !text.contains('.') { text += ".0" } - Literal { text: text.into(), id: tt::TokenId::unspecified() } + Literal { text: text.into(), span: tt::TokenId::unspecified() } } fn f32(&mut self, n: &str) -> Self::Literal { let n: f32 = n.parse().unwrap(); let text = format!("{n}f32"); - Literal { text: text.into(), id: tt::TokenId::unspecified() } + Literal { text: text.into(), span: tt::TokenId::unspecified() } } fn f64(&mut self, n: &str) -> Self::Literal { let n: f64 = n.parse().unwrap(); let text = format!("{n}f64"); - Literal { text: text.into(), id: tt::TokenId::unspecified() } + Literal { text: text.into(), span: tt::TokenId::unspecified() } } fn string(&mut self, string: &str) -> Self::Literal { @@ -578,11 +579,11 @@ impl server::Literal for RustAnalyzer { for ch in string.chars() { escaped.extend(ch.escape_debug()); } - Literal { text: format!("\"{escaped}\"").into(), id: tt::TokenId::unspecified() } + Literal { text: format!("\"{escaped}\"").into(), span: tt::TokenId::unspecified() } } fn character(&mut self, ch: char) -> Self::Literal { - Literal { text: format!("'{ch}'").into(), id: tt::TokenId::unspecified() } + Literal { text: format!("'{ch}'").into(), span: tt::TokenId::unspecified() } } fn byte_string(&mut self, bytes: &[u8]) -> Self::Literal { @@ -593,15 +594,15 @@ impl server::Literal for RustAnalyzer { .map(Into::<char>::into) .collect::<String>(); - Literal { text: format!("b\"{string}\"").into(), id: tt::TokenId::unspecified() } + Literal { text: format!("b\"{string}\"").into(), span: tt::TokenId::unspecified() } } fn span(&mut self, literal: &Self::Literal) -> Self::Span { - literal.id + literal.span } fn set_span(&mut self, literal: &mut Self::Literal, span: Self::Span) { - literal.id = span; + literal.span = span; } fn subspan( @@ -784,17 +785,18 @@ mod tests { token_trees: vec![ tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text: "struct".into(), - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), })), tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text: "T".into(), - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), })), tt::TokenTree::Subtree(tt::Subtree { - delimiter: Some(tt::Delimiter { - id: tt::TokenId::unspecified(), + delimiter: tt::Delimiter { + open: tt::TokenId::unspecified(), + close: tt::TokenId::unspecified(), kind: tt::DelimiterKind::Brace, - }), + }, token_trees: vec![], }), ], @@ -807,13 +809,14 @@ mod tests { fn test_ra_server_from_str() { use std::str::FromStr; let subtree_paren_a = tt::TokenTree::Subtree(tt::Subtree { - delimiter: Some(tt::Delimiter { - id: tt::TokenId::unspecified(), + delimiter: tt::Delimiter { + open: tt::TokenId::unspecified(), + close: tt::TokenId::unspecified(), kind: tt::DelimiterKind::Parenthesis, - }), + }, token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text: "a".into(), - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), }))], }); @@ -830,7 +833,7 @@ mod tests { underscore.token_trees[0], tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text: "_".into(), - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), })) ); } diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_sysroot/mod.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_sysroot/mod.rs index 243972b04..0a3b8866a 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_sysroot/mod.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_sysroot/mod.rs @@ -9,7 +9,7 @@ mod ra_server; use libloading::Library; use proc_macro_api::ProcMacroKind; -use super::PanicMessage; +use super::{tt, PanicMessage}; pub use ra_server::TokenStream; diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_sysroot/ra_server.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_sysroot/ra_server.rs index 068f79f82..d258a0247 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_sysroot/ra_server.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_sysroot/ra_server.rs @@ -22,6 +22,8 @@ pub use symbol::*; use std::ops::Bound; +use crate::tt; + type Group = tt::Subtree; type TokenTree = tt::TokenTree; type Punct = tt::Punct; @@ -108,8 +110,9 @@ impl server::TokenStream for RustAnalyzer { bridge::TokenTree::Ident(ident) => { let text = ident.sym.text(); - let text = if ident.is_raw { tt::SmolStr::from_iter(["r#", &text]) } else { text }; - let ident: tt::Ident = tt::Ident { text, id: ident.span }; + let text = + if ident.is_raw { ::tt::SmolStr::from_iter(["r#", &text]) } else { text }; + let ident: tt::Ident = tt::Ident { text, span: ident.span }; let leaf = tt::Leaf::from(ident); let tree = TokenTree::from(leaf); Self::TokenStream::from_iter(vec![tree]) @@ -118,9 +121,9 @@ impl server::TokenStream for RustAnalyzer { bridge::TokenTree::Literal(literal) => { let literal = LiteralFormatter(literal); let text = literal - .with_stringify_parts(|parts| tt::SmolStr::from_iter(parts.iter().copied())); + .with_stringify_parts(|parts| ::tt::SmolStr::from_iter(parts.iter().copied())); - let literal = tt::Literal { text, id: literal.0.span }; + let literal = tt::Literal { text, span: literal.0.span }; let leaf = tt::Leaf::from(literal); let tree = TokenTree::from(leaf); Self::TokenStream::from_iter(vec![tree]) @@ -130,7 +133,7 @@ impl server::TokenStream for RustAnalyzer { let punct = tt::Punct { char: p.ch as char, spacing: if p.joint { Spacing::Joint } else { Spacing::Alone }, - id: p.span, + span: p.span, }; let leaf = tt::Leaf::from(punct); let tree = TokenTree::from(leaf); @@ -184,7 +187,7 @@ impl server::TokenStream for RustAnalyzer { bridge::TokenTree::Ident(bridge::Ident { sym: Symbol::intern(ident.text.trim_start_matches("r#")), is_raw: ident.text.starts_with("r#"), - span: ident.id, + span: ident.span, }) } tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => { @@ -194,14 +197,14 @@ impl server::TokenStream for RustAnalyzer { symbol: Symbol::intern(&lit.text), // FIXME: handle suffixes suffix: None, - span: lit.id, + span: lit.span, }) } tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) => { bridge::TokenTree::Punct(bridge::Punct { ch: punct.char as u8, joint: punct.spacing == Spacing::Joint, - span: punct.id, + span: punct.span, }) } tt::TokenTree::Subtree(subtree) => bridge::TokenTree::Group(bridge::Group { @@ -211,31 +214,29 @@ impl server::TokenStream for RustAnalyzer { } else { Some(subtree.token_trees.into_iter().collect()) }, - span: bridge::DelimSpan::from_single( - subtree.delimiter.map_or(Span::unspecified(), |del| del.id), - ), + span: bridge::DelimSpan::from_single(subtree.delimiter.open), }), }) .collect() } } -fn delim_to_internal(d: proc_macro::Delimiter) -> Option<tt::Delimiter> { +fn delim_to_internal(d: proc_macro::Delimiter) -> tt::Delimiter { let kind = match d { proc_macro::Delimiter::Parenthesis => tt::DelimiterKind::Parenthesis, proc_macro::Delimiter::Brace => tt::DelimiterKind::Brace, proc_macro::Delimiter::Bracket => tt::DelimiterKind::Bracket, - proc_macro::Delimiter::None => return None, + proc_macro::Delimiter::None => tt::DelimiterKind::Invisible, }; - Some(tt::Delimiter { id: tt::TokenId::unspecified(), kind }) + tt::Delimiter { open: tt::TokenId::unspecified(), close: tt::TokenId::unspecified(), kind } } -fn delim_to_external(d: Option<tt::Delimiter>) -> proc_macro::Delimiter { - match d.map(|it| it.kind) { - Some(tt::DelimiterKind::Parenthesis) => proc_macro::Delimiter::Parenthesis, - Some(tt::DelimiterKind::Brace) => proc_macro::Delimiter::Brace, - Some(tt::DelimiterKind::Bracket) => proc_macro::Delimiter::Bracket, - None => proc_macro::Delimiter::None, +fn delim_to_external(d: tt::Delimiter) -> proc_macro::Delimiter { + match d.kind { + tt::DelimiterKind::Parenthesis => proc_macro::Delimiter::Parenthesis, + tt::DelimiterKind::Brace => proc_macro::Delimiter::Brace, + tt::DelimiterKind::Bracket => proc_macro::Delimiter::Bracket, + tt::DelimiterKind::Invisible => proc_macro::Delimiter::None, } } @@ -349,7 +350,7 @@ impl server::Server for RustAnalyzer { } fn intern_symbol(ident: &str) -> Self::Symbol { - Symbol::intern(&tt::SmolStr::from(ident)) + Symbol::intern(&::tt::SmolStr::from(ident)) } fn with_symbol_string(symbol: &Self::Symbol, f: impl FnOnce(&str)) { @@ -413,17 +414,18 @@ mod tests { token_trees: vec![ tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text: "struct".into(), - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), })), tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text: "T".into(), - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), })), tt::TokenTree::Subtree(tt::Subtree { - delimiter: Some(tt::Delimiter { - id: tt::TokenId::unspecified(), + delimiter: tt::Delimiter { + open: tt::TokenId::unspecified(), + close: tt::TokenId::unspecified(), kind: tt::DelimiterKind::Brace, - }), + }, token_trees: vec![], }), ], @@ -436,13 +438,14 @@ mod tests { fn test_ra_server_from_str() { use std::str::FromStr; let subtree_paren_a = tt::TokenTree::Subtree(tt::Subtree { - delimiter: Some(tt::Delimiter { - id: tt::TokenId::unspecified(), + delimiter: tt::Delimiter { + open: tt::TokenId::unspecified(), + close: tt::TokenId::unspecified(), kind: tt::DelimiterKind::Parenthesis, - }), + }, token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text: "a".into(), - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), }))], }); @@ -459,7 +462,7 @@ mod tests { underscore.token_trees[0], tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { text: "_".into(), - id: tt::TokenId::unspecified(), + span: tt::TokenId::unspecified(), })) ); } diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_sysroot/ra_server/token_stream.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_sysroot/ra_server/token_stream.rs index 113bb52c1..d091d4319 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_sysroot/ra_server/token_stream.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/abi_sysroot/ra_server/token_stream.rs @@ -1,6 +1,6 @@ //! TokenStream implementation used by sysroot ABI -use tt::TokenTree; +use crate::tt::{self, TokenTree}; #[derive(Debug, Default, Clone)] pub struct TokenStream { @@ -13,7 +13,7 @@ impl TokenStream { } pub fn with_subtree(subtree: tt::Subtree) -> Self { - if subtree.delimiter.is_some() { + if subtree.delimiter.kind != tt::DelimiterKind::Invisible { TokenStream { token_trees: vec![TokenTree::Subtree(subtree)] } } else { TokenStream { token_trees: subtree.token_trees } @@ -21,7 +21,7 @@ impl TokenStream { } pub fn into_subtree(self) -> tt::Subtree { - tt::Subtree { delimiter: None, token_trees: self.token_trees } + tt::Subtree { delimiter: tt::Delimiter::UNSPECIFIED, token_trees: self.token_trees } } pub fn is_empty(&self) -> bool { @@ -64,7 +64,9 @@ impl Extend<TokenStream> for TokenStream { for item in streams { for tkn in item { match tkn { - tt::TokenTree::Subtree(subtree) if subtree.delimiter.is_none() => { + tt::TokenTree::Subtree(subtree) + if subtree.delimiter.kind == tt::DelimiterKind::Invisible => + { self.token_trees.extend(subtree.token_trees); } _ => { @@ -84,7 +86,7 @@ pub struct TokenStreamBuilder { pub mod token_stream { use std::str::FromStr; - use super::{TokenStream, TokenTree}; + use super::{tt, TokenStream, TokenTree}; /// An iterator over `TokenStream`'s `TokenTree`s. /// The iteration is "shallow", e.g., the iterator doesn't recurse into delimited groups, @@ -121,15 +123,17 @@ pub mod token_stream { impl ToString for TokenStream { fn to_string(&self) -> String { - tt::pretty(&self.token_trees) + ::tt::pretty(&self.token_trees) } } fn subtree_replace_token_ids_with_unspecified(subtree: tt::Subtree) -> tt::Subtree { tt::Subtree { - delimiter: subtree - .delimiter - .map(|d| tt::Delimiter { id: tt::TokenId::unspecified(), ..d }), + delimiter: tt::Delimiter { + open: tt::TokenId::UNSPECIFIED, + close: tt::TokenId::UNSPECIFIED, + ..subtree.delimiter + }, token_trees: subtree .token_trees .into_iter() @@ -152,13 +156,13 @@ pub mod token_stream { fn leaf_replace_token_ids_with_unspecified(leaf: tt::Leaf) -> tt::Leaf { match leaf { tt::Leaf::Literal(lit) => { - tt::Leaf::Literal(tt::Literal { id: tt::TokenId::unspecified(), ..lit }) + tt::Leaf::Literal(tt::Literal { span: tt::TokenId::unspecified(), ..lit }) } tt::Leaf::Punct(punct) => { - tt::Leaf::Punct(tt::Punct { id: tt::TokenId::unspecified(), ..punct }) + tt::Leaf::Punct(tt::Punct { span: tt::TokenId::unspecified(), ..punct }) } tt::Leaf::Ident(ident) => { - tt::Leaf::Ident(tt::Ident { id: tt::TokenId::unspecified(), ..ident }) + tt::Leaf::Ident(tt::Ident { span: tt::TokenId::unspecified(), ..ident }) } } } diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/mod.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/mod.rs index 5b8aca4d8..04be39cff 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/mod.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/abis/mod.rs @@ -23,7 +23,6 @@ //! for the relevant versions of the rust compiler //! -mod abi_1_58; mod abi_1_63; #[cfg(feature = "sysroot-abi")] mod abi_sysroot; @@ -36,13 +35,14 @@ include!(concat!(env!("OUT_DIR"), "/rustc_version.rs")); pub(crate) use abi_sysroot::TokenStream as TestTokenStream; use super::dylib::LoadProcMacroDylibError; -pub(crate) use abi_1_58::Abi as Abi_1_58; pub(crate) use abi_1_63::Abi as Abi_1_63; #[cfg(feature = "sysroot-abi")] pub(crate) use abi_sysroot::Abi as Abi_Sysroot; use libloading::Library; use proc_macro_api::{ProcMacroKind, RustCInfo}; +use crate::tt; + pub struct PanicMessage { message: Option<String>, } @@ -54,7 +54,6 @@ impl PanicMessage { } pub(crate) enum Abi { - Abi1_58(Abi_1_58), Abi1_63(Abi_1_63), #[cfg(feature = "sysroot-abi")] AbiSysroot(Abi_Sysroot), @@ -109,10 +108,6 @@ impl Abi { // FIXME: this should use exclusive ranges when they're stable // https://github.com/rust-lang/rust/issues/37854 match (info.version.0, info.version.1) { - (1, 58..=62) => { - let inner = unsafe { Abi_1_58::from_lib(lib, symbol_name) }?; - Ok(Abi::Abi1_58(inner)) - } (1, 63) => { let inner = unsafe { Abi_1_63::from_lib(lib, symbol_name) }?; Ok(Abi::Abi1_63(inner)) @@ -128,7 +123,6 @@ impl Abi { attributes: Option<&tt::Subtree>, ) -> Result<tt::Subtree, PanicMessage> { match self { - Self::Abi1_58(abi) => abi.expand(macro_name, macro_body, attributes), Self::Abi1_63(abi) => abi.expand(macro_name, macro_body, attributes), #[cfg(feature = "sysroot-abi")] Self::AbiSysroot(abi) => abi.expand(macro_name, macro_body, attributes), @@ -137,7 +131,6 @@ impl Abi { pub fn list_macros(&self) -> Vec<(String, ProcMacroKind)> { match self { - Self::Abi1_58(abi) => abi.list_macros(), Self::Abi1_63(abi) => abi.list_macros(), #[cfg(feature = "sysroot-abi")] Self::AbiSysroot(abi) => abi.list_macros(), diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/cli.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/cli.rs index f1e131c13..05168feb6 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/cli.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/cli.rs @@ -15,6 +15,9 @@ pub fn run() -> io::Result<()> { msg::Response::ListMacros(srv.list_macros(&dylib_path)) } msg::Request::ExpandMacro(task) => msg::Response::ExpandMacro(srv.expand(task)), + msg::Request::ApiVersionCheck {} => { + msg::Response::ApiVersionCheck(proc_macro_api::msg::CURRENT_API_VERSION) + } }; write_response(res)? } diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs index 0722cd89d..89ffd1f49 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/dylib.rs @@ -13,6 +13,8 @@ use object::Object; use paths::AbsPath; use proc_macro_api::{read_dylib_info, ProcMacroKind}; +use crate::tt; + use super::abis::Abi; const NEW_REGISTRAR_SYMBOL: &str = "_rustc_proc_macro_decls_"; diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs index 2eb939a7c..ee70fe7d4 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs @@ -37,6 +37,8 @@ use proc_macro_api::{ ProcMacroKind, }; +use ::tt::token_id as tt; + #[derive(Default)] pub(crate) struct ProcMacroSrv { expanders: HashMap<(PathBuf, SystemTime), dylib::Expander>, diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs index 1ccc170f4..04a0ae7bc 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs @@ -8,7 +8,7 @@ use expect_test::expect; #[test] fn test_derive_empty() { - assert_expand("DeriveEmpty", r#"struct S;"#, expect![[r#"SUBTREE $"#]]); + assert_expand("DeriveEmpty", r#"struct S;"#, expect!["SUBTREE $$ 4294967295 4294967295"]); } #[test] @@ -17,10 +17,10 @@ fn test_derive_error() { "DeriveError", r#"struct S;"#, expect![[r##" - SUBTREE $ + SUBTREE $$ 4294967295 4294967295 IDENT compile_error 4294967295 PUNCH ! [alone] 4294967295 - SUBTREE () 4294967295 + SUBTREE () 4294967295 4294967295 LITERAL "#[derive(DeriveError)] struct S ;" 4294967295 PUNCH ; [alone] 4294967295"##]], ); @@ -32,14 +32,14 @@ fn test_fn_like_macro_noop() { "fn_like_noop", r#"ident, 0, 1, []"#, expect![[r#" - SUBTREE $ + SUBTREE $$ 4294967295 4294967295 IDENT ident 4294967295 PUNCH , [alone] 4294967295 LITERAL 0 4294967295 PUNCH , [alone] 4294967295 LITERAL 1 4294967295 PUNCH , [alone] 4294967295 - SUBTREE [] 4294967295"#]], + SUBTREE [] 4294967295 4294967295"#]], ); } @@ -49,10 +49,10 @@ fn test_fn_like_macro_clone_ident_subtree() { "fn_like_clone_tokens", r#"ident, []"#, expect![[r#" - SUBTREE $ + SUBTREE $$ 4294967295 4294967295 IDENT ident 4294967295 PUNCH , [alone] 4294967295 - SUBTREE [] 4294967295"#]], + SUBTREE [] 4294967295 4294967295"#]], ); } @@ -62,7 +62,7 @@ fn test_fn_like_macro_clone_raw_ident() { "fn_like_clone_tokens", "r#async", expect![[r#" - SUBTREE $ + SUBTREE $$ 4294967295 4294967295 IDENT r#async 4294967295"#]], ); } @@ -73,7 +73,7 @@ fn test_fn_like_mk_literals() { "fn_like_mk_literals", r#""#, expect![[r#" - SUBTREE $ + SUBTREE $$ 4294967295 4294967295 LITERAL b"byte_string" 4294967295 LITERAL 'c' 4294967295 LITERAL "string" 4294967295 @@ -90,7 +90,7 @@ fn test_fn_like_mk_idents() { "fn_like_mk_idents", r#""#, expect![[r#" - SUBTREE $ + SUBTREE $$ 4294967295 4294967295 IDENT standard 4294967295 IDENT r#raw 4294967295"#]], ); @@ -102,7 +102,7 @@ fn test_fn_like_macro_clone_literals() { "fn_like_clone_tokens", r#"1u16, 2_u32, -4i64, 3.14f32, "hello bridge""#, expect![[r#" - SUBTREE $ + SUBTREE $$ 4294967295 4294967295 LITERAL 1u16 4294967295 PUNCH , [alone] 4294967295 LITERAL 2_u32 4294967295 @@ -126,10 +126,10 @@ fn test_attr_macro() { r#"mod m {}"#, r#"some arguments"#, expect![[r##" - SUBTREE $ + SUBTREE $$ 4294967295 4294967295 IDENT compile_error 4294967295 PUNCH ! [alone] 4294967295 - SUBTREE () 4294967295 + SUBTREE () 4294967295 4294967295 LITERAL "#[attr_error(some arguments)] mod m {}" 4294967295 PUNCH ; [alone] 4294967295"##]], ); diff --git a/src/tools/rust-analyzer/crates/proc-macro-test/Cargo.toml b/src/tools/rust-analyzer/crates/proc-macro-test/Cargo.toml index d2a79f910..77b4afd7d 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-test/Cargo.toml +++ b/src/tools/rust-analyzer/crates/proc-macro-test/Cargo.toml @@ -1,15 +1,20 @@ [package] name = "proc-macro-test" version = "0.0.0" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" publish = false +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true + [lib] doctest = false [build-dependencies] -proc-macro-test-impl = { path = "imp", version = "0.0.0" } -toolchain = { path = "../toolchain", version = "0.0.0" } cargo_metadata = "0.15.0" + +proc-macro-test-impl = { path = "imp", version = "0.0.0" } + +# local deps +toolchain.workspace = true diff --git a/src/tools/rust-analyzer/crates/proc-macro-test/imp/Cargo.toml b/src/tools/rust-analyzer/crates/proc-macro-test/imp/Cargo.toml index 1bd14070e..2a36737ce 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-test/imp/Cargo.toml +++ b/src/tools/rust-analyzer/crates/proc-macro-test/imp/Cargo.toml @@ -3,7 +3,6 @@ name = "proc-macro-test-impl" version = "0.0.0" license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" publish = false [lib] diff --git a/src/tools/rust-analyzer/crates/profile/Cargo.toml b/src/tools/rust-analyzer/crates/profile/Cargo.toml index 01d1735bf..6273ea51d 100644 --- a/src/tools/rust-analyzer/crates/profile/Cargo.toml +++ b/src/tools/rust-analyzer/crates/profile/Cargo.toml @@ -2,15 +2,17 @@ name = "profile" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false [dependencies] -once_cell = "1.15.0" +once_cell = "1.17.0" cfg-if = "1.0.0" libc = "0.2.135" la-arena = { version = "0.3.0", path = "../../lib/la-arena" } diff --git a/src/tools/rust-analyzer/crates/profile/src/lib.rs b/src/tools/rust-analyzer/crates/profile/src/lib.rs index 7ca3c7d62..e7fc3d970 100644 --- a/src/tools/rust-analyzer/crates/profile/src/lib.rs +++ b/src/tools/rust-analyzer/crates/profile/src/lib.rs @@ -26,7 +26,7 @@ pub use countme::Count; thread_local!(static IN_SCOPE: RefCell<bool> = RefCell::new(false)); -/// Allows to check if the current code is withing some dynamic scope, can be +/// Allows to check if the current code is within some dynamic scope, can be /// useful during debugging to figure out why a function is called. pub struct Scope { prev: bool, diff --git a/src/tools/rust-analyzer/crates/project-model/Cargo.toml b/src/tools/rust-analyzer/crates/project-model/Cargo.toml index 39902a532..22d6a6e78 100644 --- a/src/tools/rust-analyzer/crates/project-model/Cargo.toml +++ b/src/tools/rust-analyzer/crates/project-model/Cargo.toml @@ -2,9 +2,11 @@ name = "project-model" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -19,12 +21,13 @@ serde_json = "1.0.86" anyhow = "1.0.62" la-arena = { version = "0.3.0", path = "../../lib/la-arena" } -cfg = { path = "../cfg", version = "0.0.0" } -base-db = { path = "../base-db", version = "0.0.0" } -toolchain = { path = "../toolchain", version = "0.0.0" } -paths = { path = "../paths", version = "0.0.0" } -stdx = { path = "../stdx", version = "0.0.0" } -profile = { path = "../profile", version = "0.0.0" } +# local deps +base-db.workspace = true +cfg.workspace = true +paths.workspace = true +profile.workspace = true +stdx.workspace = true +toolchain.workspace = true [dev-dependencies] expect-test = "1.4.0" diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs index 467cf0917..fdc7859eb 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs @@ -96,6 +96,7 @@ pub struct CargoConfig { pub target: Option<String>, /// Sysroot loading behavior pub sysroot: Option<RustcSource>, + pub sysroot_src: Option<AbsPathBuf>, /// rustc private crate source pub rustc_source: Option<RustcSource>, /// crates to disable `#[cfg(test)]` on diff --git a/src/tools/rust-analyzer/crates/project-model/src/lib.rs b/src/tools/rust-analyzer/crates/project-model/src/lib.rs index e2f09bad2..9b6a71db8 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/lib.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/lib.rs @@ -25,7 +25,7 @@ mod sysroot; mod workspace; mod rustc_cfg; mod build_scripts; -mod target_data_layout; +pub mod target_data_layout; #[cfg(test)] mod tests; diff --git a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs index 9af0eafe9..4b2448e47 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/project_json.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/project_json.rs @@ -4,6 +4,50 @@ //! idea here is that people who do not use Cargo, can instead teach their build //! system to generate `rust-project.json` which can be ingested by //! rust-analyzer. +//! +//! This short file is a somewhat big conceptual piece of the architecture of +//! rust-analyzer, so it's worth elaborating on the underlying ideas and +//! motivation. +//! +//! For rust-analyzer to function, it needs some information about the project. +//! Specifically, it maintains an in-memory data structure which lists all the +//! crates (compilation units) and dependencies between them. This is necessary +//! a global singleton, as we do want, eg, find usages to always search across +//! the whole project, rather than just in the "current" crate. +//! +//! Normally, we get this "crate graph" by calling `cargo metadata +//! --message-format=json` for each cargo workspace and merging results. This +//! works for your typical cargo project, but breaks down for large folks who +//! have a monorepo with an infinite amount of Rust code which is built with bazel or +//! some such. +//! +//! To support this use case, we need to make _something_ configurable. To avoid +//! a [midlayer mistake](https://lwn.net/Articles/336262/), we allow configuring +//! the lowest possible layer. `ProjectJson` is essentially a hook to just set +//! that global singleton in-memory data structure. It is optimized for power, +//! not for convenience (you'd be using cargo anyway if you wanted nice things, +//! right? :) +//! +//! `rust-project.json` also isn't necessary a file. Architecturally, we support +//! any convenient way to specify this data, which today is: +//! +//! * file on disk +//! * a field in the config (ie, you can send a JSON request with the contents +//! of rust-project.json to rust-analyzer, no need to write anything to disk) +//! +//! Another possible thing we don't do today, but which would be totally valid, +//! is to add an extension point to VS Code extension to register custom +//! project. +//! +//! In general, it is assumed that if you are going to use `rust-project.json`, +//! you'd write a fair bit of custom code gluing your build system to ra through +//! this JSON format. This logic can take form of a VS Code extension, or a +//! proxy process which injects data into "configure" LSP request, or maybe just +//! a simple build system rule to generate the file. +//! +//! In particular, the logic for lazily loading parts of the monorepo as the +//! user explores them belongs to that extension (it's totally valid to change +//! rust-project.json over time via configuration request!) use std::path::PathBuf; diff --git a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs index 8d5ab0061..328d2fbcf 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs @@ -7,6 +7,7 @@ use std::{env, fs, iter, ops, path::PathBuf, process::Command}; use anyhow::{format_err, Result}; +use base_db::CrateName; use la_arena::{Arena, Idx}; use paths::{AbsPath, AbsPathBuf}; use rustc_hash::FxHashMap; @@ -50,14 +51,16 @@ impl Sysroot { &self.src_root } - pub fn public_deps(&self) -> impl Iterator<Item = (&'static str, SysrootCrate, bool)> + '_ { + pub fn public_deps(&self) -> impl Iterator<Item = (CrateName, SysrootCrate, bool)> + '_ { // core is added as a dependency before std in order to // mimic rustcs dependency order ["core", "alloc", "std"] .into_iter() .zip(iter::repeat(true)) .chain(iter::once(("test", false))) - .filter_map(move |(name, prelude)| Some((name, self.by_name(name)?, prelude))) + .filter_map(move |(name, prelude)| { + Some((CrateName::new(name).unwrap(), self.by_name(name)?, prelude)) + }) } pub fn proc_macro(&self) -> Option<SysrootCrate> { @@ -67,8 +70,13 @@ impl Sysroot { pub fn crates(&self) -> impl Iterator<Item = SysrootCrate> + ExactSizeIterator + '_ { self.crates.iter().map(|(id, _data)| id) } + + pub fn is_empty(&self) -> bool { + self.crates.is_empty() + } } +// FIXME: Expose a builder api as loading the sysroot got way too modular and complicated. impl Sysroot { /// Attempts to discover the toolchain's sysroot from the given `dir`. pub fn discover(dir: &AbsPath, extra_env: &FxHashMap<String, String>) -> Result<Sysroot> { @@ -76,8 +84,17 @@ impl Sysroot { let sysroot_dir = discover_sysroot_dir(dir, extra_env)?; let sysroot_src_dir = discover_sysroot_src_dir_or_add_component(&sysroot_dir, dir, extra_env)?; - let res = Sysroot::load(sysroot_dir, sysroot_src_dir)?; - Ok(res) + Ok(Sysroot::load(sysroot_dir, sysroot_src_dir)) + } + + pub fn discover_with_src_override( + dir: &AbsPath, + extra_env: &FxHashMap<String, String>, + src: AbsPathBuf, + ) -> Result<Sysroot> { + tracing::debug!("discovering sysroot for {}", dir.display()); + let sysroot_dir = discover_sysroot_dir(dir, extra_env)?; + Ok(Sysroot::load(sysroot_dir, src)) } pub fn discover_rustc( @@ -94,11 +111,10 @@ impl Sysroot { let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir).ok_or_else(|| { format_err!("can't load standard library from sysroot {}", sysroot_dir.display()) })?; - let res = Sysroot::load(sysroot_dir, sysroot_src_dir)?; - Ok(res) + Ok(Sysroot::load(sysroot_dir, sysroot_src_dir)) } - pub fn load(sysroot_dir: AbsPathBuf, sysroot_src_dir: AbsPathBuf) -> Result<Sysroot> { + pub fn load(sysroot_dir: AbsPathBuf, sysroot_src_dir: AbsPathBuf) -> Sysroot { let mut sysroot = Sysroot { root: sysroot_dir, src_root: sysroot_src_dir, crates: Arena::default() }; @@ -149,14 +165,14 @@ impl Sysroot { } else { "" }; - anyhow::bail!( + tracing::error!( "could not find libcore in sysroot path `{}`{}", sysroot.src_root.as_path().display(), var_note, ); } - Ok(sysroot) + sysroot } fn by_name(&self, name: &str) -> Option<SysrootCrate> { diff --git a/src/tools/rust-analyzer/crates/project-model/src/target_data_layout.rs b/src/tools/rust-analyzer/crates/project-model/src/target_data_layout.rs index 40cf47c3f..42c06ad0e 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/target_data_layout.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/target_data_layout.rs @@ -1,15 +1,16 @@ //! Runs `rustc --print target-spec-json` to get the target_data_layout. use std::process::Command; +use anyhow::Result; use rustc_hash::FxHashMap; use crate::{utf8_stdout, ManifestPath}; -pub(super) fn get( +pub fn get( cargo_toml: Option<&ManifestPath>, target: Option<&str>, extra_env: &FxHashMap<String, String>, -) -> Option<String> { +) -> Result<String> { let output = (|| { if let Some(cargo_toml) = cargo_toml { let mut cmd = Command::new(toolchain::rustc()); @@ -28,13 +29,13 @@ pub(super) fn get( // using unstable cargo features failed, fall back to using plain rustc let mut cmd = Command::new(toolchain::rustc()); cmd.envs(extra_env) - .args(["-Z", "unstable-options", "rustc", "--print", "target-spec-json"]) + .args(["-Z", "unstable-options", "--print", "target-spec-json"]) .env("RUSTC_BOOTSTRAP", "1"); if let Some(target) = target { cmd.args(["--target", target]); } utf8_stdout(cmd) - })() - .ok()?; - Some(output.split_once(r#""data-layout": ""#)?.1.split_once('"')?.0.to_owned()) + })()?; + (|| Some(output.split_once(r#""data-layout": ""#)?.1.split_once('"')?.0.to_owned()))() + .ok_or_else(|| anyhow::format_err!("could not fetch target-spec-json from command output")) } diff --git a/src/tools/rust-analyzer/crates/project-model/src/tests.rs b/src/tools/rust-analyzer/crates/project-model/src/tests.rs index 2bb9ebf99..9e9691d11 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/tests.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/tests.rs @@ -29,7 +29,7 @@ fn load_cargo_with_overrides(file: &str, cfg_overrides: CfgOverrides) -> CrateGr rustc_cfg: Vec::new(), cfg_overrides, toolchain: None, - target_layout: None, + target_layout: Err("target_data_layout not loaded".into()), }; to_crate_graph(project_workspace) } @@ -81,7 +81,7 @@ fn get_fake_sysroot() -> Sysroot { // fake sysroot, so we give them both the same path: let sysroot_dir = AbsPathBuf::assert(sysroot_path); let sysroot_src_dir = sysroot_dir.clone(); - Sysroot::load(sysroot_dir, sysroot_src_dir).unwrap() + Sysroot::load(sysroot_dir, sysroot_src_dir) } fn rooted_project_json(data: ProjectJsonData) -> ProjectJson { @@ -151,7 +151,9 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() { "debug_assertions", ], ), - target_layout: None, + target_layout: Err( + "target_data_layout not loaded", + ), env: Env { entries: { "CARGO_PKG_LICENSE": "", @@ -221,7 +223,9 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() { "debug_assertions", ], ), - target_layout: None, + target_layout: Err( + "target_data_layout not loaded", + ), env: Env { entries: { "CARGO_PKG_LICENSE": "", @@ -300,7 +304,9 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() { "debug_assertions", ], ), - target_layout: None, + target_layout: Err( + "target_data_layout not loaded", + ), env: Env { entries: { "CARGO_PKG_LICENSE": "", @@ -379,7 +385,9 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() { "debug_assertions", ], ), - target_layout: None, + target_layout: Err( + "target_data_layout not loaded", + ), env: Env { entries: { "CARGO_PKG_LICENSE": "", @@ -467,7 +475,9 @@ fn cargo_hello_world_project_model_with_wildcard_overrides() { "feature=use_std", ], ), - target_layout: None, + target_layout: Err( + "target_data_layout not loaded", + ), env: Env { entries: { "CARGO_PKG_LICENSE": "", @@ -553,7 +563,9 @@ fn cargo_hello_world_project_model_with_selective_overrides() { "test", ], ), - target_layout: None, + target_layout: Err( + "target_data_layout not loaded", + ), env: Env { entries: { "CARGO_PKG_LICENSE": "", @@ -625,7 +637,9 @@ fn cargo_hello_world_project_model_with_selective_overrides() { "test", ], ), - target_layout: None, + target_layout: Err( + "target_data_layout not loaded", + ), env: Env { entries: { "CARGO_PKG_LICENSE": "", @@ -706,7 +720,9 @@ fn cargo_hello_world_project_model_with_selective_overrides() { "test", ], ), - target_layout: None, + target_layout: Err( + "target_data_layout not loaded", + ), env: Env { entries: { "CARGO_PKG_LICENSE": "", @@ -787,7 +803,9 @@ fn cargo_hello_world_project_model_with_selective_overrides() { "test", ], ), - target_layout: None, + target_layout: Err( + "target_data_layout not loaded", + ), env: Env { entries: { "CARGO_PKG_LICENSE": "", @@ -875,7 +893,9 @@ fn cargo_hello_world_project_model_with_selective_overrides() { "feature=use_std", ], ), - target_layout: None, + target_layout: Err( + "target_data_layout not loaded", + ), env: Env { entries: { "CARGO_PKG_LICENSE": "", @@ -952,7 +972,9 @@ fn cargo_hello_world_project_model() { "test", ], ), - target_layout: None, + target_layout: Err( + "target_data_layout not loaded", + ), env: Env { entries: { "CARGO_PKG_LICENSE": "", @@ -1024,7 +1046,9 @@ fn cargo_hello_world_project_model() { "test", ], ), - target_layout: None, + target_layout: Err( + "target_data_layout not loaded", + ), env: Env { entries: { "CARGO_PKG_LICENSE": "", @@ -1105,7 +1129,9 @@ fn cargo_hello_world_project_model() { "test", ], ), - target_layout: None, + target_layout: Err( + "target_data_layout not loaded", + ), env: Env { entries: { "CARGO_PKG_LICENSE": "", @@ -1186,7 +1212,9 @@ fn cargo_hello_world_project_model() { "test", ], ), - target_layout: None, + target_layout: Err( + "target_data_layout not loaded", + ), env: Env { entries: { "CARGO_PKG_LICENSE": "", @@ -1274,7 +1302,9 @@ fn cargo_hello_world_project_model() { "feature=use_std", ], ), - target_layout: None, + target_layout: Err( + "target_data_layout not loaded", + ), env: Env { entries: { "CARGO_PKG_LICENSE": "", @@ -1327,7 +1357,7 @@ fn rust_project_hello_world_project_model() { root_file_id: FileId( 1, ), - edition: Edition2018, + edition: Edition2021, version: None, display_name: Some( CrateDisplayName { @@ -1343,7 +1373,9 @@ fn rust_project_hello_world_project_model() { potential_cfg_options: CfgOptions( [], ), - target_layout: None, + target_layout: Err( + "rust-project.json projects have no target layout set", + ), env: Env { entries: {}, }, @@ -1372,7 +1404,7 @@ fn rust_project_hello_world_project_model() { root_file_id: FileId( 2, ), - edition: Edition2018, + edition: Edition2021, version: None, display_name: Some( CrateDisplayName { @@ -1388,7 +1420,9 @@ fn rust_project_hello_world_project_model() { potential_cfg_options: CfgOptions( [], ), - target_layout: None, + target_layout: Err( + "rust-project.json projects have no target layout set", + ), env: Env { entries: {}, }, @@ -1407,7 +1441,7 @@ fn rust_project_hello_world_project_model() { root_file_id: FileId( 3, ), - edition: Edition2018, + edition: Edition2021, version: None, display_name: Some( CrateDisplayName { @@ -1423,7 +1457,9 @@ fn rust_project_hello_world_project_model() { potential_cfg_options: CfgOptions( [], ), - target_layout: None, + target_layout: Err( + "rust-project.json projects have no target layout set", + ), env: Env { entries: {}, }, @@ -1442,7 +1478,7 @@ fn rust_project_hello_world_project_model() { root_file_id: FileId( 4, ), - edition: Edition2018, + edition: Edition2021, version: None, display_name: Some( CrateDisplayName { @@ -1458,7 +1494,9 @@ fn rust_project_hello_world_project_model() { potential_cfg_options: CfgOptions( [], ), - target_layout: None, + target_layout: Err( + "rust-project.json projects have no target layout set", + ), env: Env { entries: {}, }, @@ -1477,7 +1515,7 @@ fn rust_project_hello_world_project_model() { root_file_id: FileId( 5, ), - edition: Edition2018, + edition: Edition2021, version: None, display_name: Some( CrateDisplayName { @@ -1493,7 +1531,9 @@ fn rust_project_hello_world_project_model() { potential_cfg_options: CfgOptions( [], ), - target_layout: None, + target_layout: Err( + "rust-project.json projects have no target layout set", + ), env: Env { entries: {}, }, @@ -1522,7 +1562,7 @@ fn rust_project_hello_world_project_model() { root_file_id: FileId( 6, ), - edition: Edition2018, + edition: Edition2021, version: None, display_name: Some( CrateDisplayName { @@ -1538,7 +1578,9 @@ fn rust_project_hello_world_project_model() { potential_cfg_options: CfgOptions( [], ), - target_layout: None, + target_layout: Err( + "rust-project.json projects have no target layout set", + ), env: Env { entries: {}, }, @@ -1557,7 +1599,7 @@ fn rust_project_hello_world_project_model() { root_file_id: FileId( 7, ), - edition: Edition2018, + edition: Edition2021, version: None, display_name: Some( CrateDisplayName { @@ -1573,7 +1615,9 @@ fn rust_project_hello_world_project_model() { potential_cfg_options: CfgOptions( [], ), - target_layout: None, + target_layout: Err( + "rust-project.json projects have no target layout set", + ), env: Env { entries: {}, }, @@ -1665,7 +1709,7 @@ fn rust_project_hello_world_project_model() { root_file_id: FileId( 8, ), - edition: Edition2018, + edition: Edition2021, version: None, display_name: Some( CrateDisplayName { @@ -1681,7 +1725,9 @@ fn rust_project_hello_world_project_model() { potential_cfg_options: CfgOptions( [], ), - target_layout: None, + target_layout: Err( + "rust-project.json projects have no target layout set", + ), env: Env { entries: {}, }, @@ -1700,7 +1746,7 @@ fn rust_project_hello_world_project_model() { root_file_id: FileId( 9, ), - edition: Edition2018, + edition: Edition2021, version: None, display_name: Some( CrateDisplayName { @@ -1716,7 +1762,9 @@ fn rust_project_hello_world_project_model() { potential_cfg_options: CfgOptions( [], ), - target_layout: None, + target_layout: Err( + "rust-project.json projects have no target layout set", + ), env: Env { entries: {}, }, @@ -1735,7 +1783,7 @@ fn rust_project_hello_world_project_model() { root_file_id: FileId( 10, ), - edition: Edition2018, + edition: Edition2021, version: None, display_name: Some( CrateDisplayName { @@ -1751,7 +1799,9 @@ fn rust_project_hello_world_project_model() { potential_cfg_options: CfgOptions( [], ), - target_layout: None, + target_layout: Err( + "rust-project.json projects have no target layout set", + ), env: Env { entries: {}, }, @@ -1786,7 +1836,9 @@ fn rust_project_hello_world_project_model() { potential_cfg_options: CfgOptions( [], ), - target_layout: None, + target_layout: Err( + "rust-project.json projects have no target layout set", + ), env: Env { entries: {}, }, diff --git a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs index e2382aa37..2a11f1e8e 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs @@ -7,7 +7,7 @@ use std::{collections::VecDeque, fmt, fs, process::Command, sync::Arc}; use anyhow::{format_err, Context, Result}; use base_db::{ CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Edition, Env, - FileId, LangCrateOrigin, ProcMacroLoadResult, + FileId, LangCrateOrigin, ProcMacroLoadResult, TargetLayoutLoadResult, }; use cfg::{CfgDiff, CfgOptions}; use paths::{AbsPath, AbsPathBuf}; @@ -63,7 +63,7 @@ pub struct PackageRoot { pub exclude: Vec<AbsPathBuf>, } -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone)] pub enum ProjectWorkspace { /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. Cargo { @@ -79,11 +79,10 @@ pub enum ProjectWorkspace { rustc_cfg: Vec<CfgFlag>, cfg_overrides: CfgOverrides, toolchain: Option<Version>, - target_layout: Option<String>, + target_layout: Result<String, String>, }, /// Project workspace was manually specified using a `rust-project.json` file. Json { project: ProjectJson, sysroot: Option<Sysroot>, rustc_cfg: Vec<CfgFlag> }, - // FIXME: The primary limitation of this approach is that the set of detached files needs to be fixed at the beginning. // That's not the end user experience we should strive for. // Ideally, you should be able to just open a random detached file in existing cargo projects, and get the basic features working. @@ -163,7 +162,7 @@ impl ProjectWorkspace { project_json, config.target.as_deref(), &config.extra_env, - )? + ) } ProjectManifest::CargoToml(cargo_toml) => { let cargo_version = utf8_stdout({ @@ -191,24 +190,52 @@ impl ProjectWorkspace { })?; let cargo = CargoWorkspace::new(meta); - let sysroot = match &config.sysroot { - Some(RustcSource::Path(path)) => { - Some(Sysroot::with_sysroot_dir(path.clone()).with_context(|| { - format!("Failed to find sysroot at {}.", path.display()) - })?) + let sysroot = match (&config.sysroot, &config.sysroot_src) { + (Some(RustcSource::Path(path)), None) => { + match Sysroot::with_sysroot_dir(path.clone()) { + Ok(it) => Some(it), + Err(e) => { + tracing::error!(%e, "Failed to find sysroot at {}.", path.display()); + None + } + } } - Some(RustcSource::Discover) => Some( - Sysroot::discover(cargo_toml.parent(), &config.extra_env).with_context( - || { - format!( - "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?", - cargo_toml.display() - ) - }, - )?, - ), - None => None, + (Some(RustcSource::Discover), None) => { + match Sysroot::discover(cargo_toml.parent(), &config.extra_env) { + Ok(it) => Some(it), + Err(e) => { + tracing::error!( + %e, + "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?", + cargo_toml.display() + ); + None + } + } + } + (Some(RustcSource::Path(sysroot)), Some(sysroot_src)) => { + Some(Sysroot::load(sysroot.clone(), sysroot_src.clone())) + } + (Some(RustcSource::Discover), Some(sysroot_src)) => { + match Sysroot::discover_with_src_override( + cargo_toml.parent(), + &config.extra_env, + sysroot_src.clone(), + ) { + Ok(it) => Some(it), + Err(e) => { + tracing::error!( + %e, + "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?", + cargo_toml.display() + ); + None + } + } + } + (None, _) => None, }; + if let Some(sysroot) = &sysroot { tracing::info!(src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot"); } @@ -225,18 +252,22 @@ impl ProjectWorkspace { } let rustc = match rustc_dir { - Some(rustc_dir) => Some({ - let meta = CargoWorkspace::fetch_metadata( - &rustc_dir, - cargo_toml.parent(), - config, - progress, - ) - .with_context(|| { - "Failed to read Cargo metadata for Rust sources".to_string() - })?; - CargoWorkspace::new(meta) - }), + Some(rustc_dir) => match CargoWorkspace::fetch_metadata( + &rustc_dir, + cargo_toml.parent(), + config, + progress, + ) { + Ok(meta) => Some(CargoWorkspace::new(meta)), + Err(e) => { + tracing::error!( + %e, + "Failed to read Cargo metadata from rustc source at {}", + rustc_dir.display() + ); + None + } + }, None => None, }; @@ -249,6 +280,9 @@ impl ProjectWorkspace { config.target.as_deref(), &config.extra_env, ); + if let Err(e) = &data_layout { + tracing::error!(%e, "failed fetching data layout for {cargo_toml:?} workspace"); + } ProjectWorkspace::Cargo { cargo, build_scripts: WorkspaceBuildScripts::default(), @@ -257,7 +291,7 @@ impl ProjectWorkspace { rustc_cfg, cfg_overrides, toolchain, - target_layout: data_layout, + target_layout: data_layout.map_err(|it| it.to_string()), } } }; @@ -269,15 +303,14 @@ impl ProjectWorkspace { project_json: ProjectJson, target: Option<&str>, extra_env: &FxHashMap<String, String>, - ) -> Result<ProjectWorkspace> { + ) -> ProjectWorkspace { let sysroot = match (project_json.sysroot.clone(), project_json.sysroot_src.clone()) { - (Some(sysroot), Some(sysroot_src)) => Some(Sysroot::load(sysroot, sysroot_src)?), + (Some(sysroot), Some(sysroot_src)) => Some(Sysroot::load(sysroot, sysroot_src)), (Some(sysroot), None) => { // assume sysroot is structured like rustup's and guess `sysroot_src` let sysroot_src = sysroot.join("lib").join("rustlib").join("src").join("rust").join("library"); - - Some(Sysroot::load(sysroot, sysroot_src)?) + Some(Sysroot::load(sysroot, sysroot_src)) } (None, Some(sysroot_src)) => { // assume sysroot is structured like rustup's and guess `sysroot` @@ -285,7 +318,7 @@ impl ProjectWorkspace { for _ in 0..5 { sysroot.pop(); } - Some(Sysroot::load(sysroot, sysroot_src)?) + Some(Sysroot::load(sysroot, sysroot_src)) } (None, None) => None, }; @@ -294,7 +327,7 @@ impl ProjectWorkspace { } let rustc_cfg = rustc_cfg::get(None, target, extra_env); - Ok(ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg }) + ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg } } pub fn load_detached_files( @@ -302,18 +335,29 @@ impl ProjectWorkspace { config: &CargoConfig, ) -> Result<ProjectWorkspace> { let sysroot = match &config.sysroot { - Some(RustcSource::Path(path)) => Some( - Sysroot::with_sysroot_dir(path.clone()) - .with_context(|| format!("Failed to find sysroot at {}.", path.display()))?, - ), + Some(RustcSource::Path(path)) => match Sysroot::with_sysroot_dir(path.clone()) { + Ok(it) => Some(it), + Err(e) => { + tracing::error!(%e, "Failed to find sysroot at {}.", path.display()); + None + } + }, Some(RustcSource::Discover) => { let dir = &detached_files .first() .and_then(|it| it.parent()) .ok_or_else(|| format_err!("No detached files to load"))?; - Some(Sysroot::discover(dir, &config.extra_env).with_context(|| { - format!("Failed to find sysroot in {}. Is rust-src installed?", dir.display()) - })?) + match Sysroot::discover(dir, &config.extra_env) { + Ok(it) => Some(it), + Err(e) => { + tracing::error!( + %e, + "Failed to find sysroot for {}. Is rust-src installed?", + dir.display() + ); + None + } + } } None => None, }; @@ -366,7 +410,7 @@ impl ProjectWorkspace { _ => None, }) .collect(); - let ref mut outputs = match WorkspaceBuildScripts::run_once(config, &cargo_ws, progress) { + let outputs = &mut match WorkspaceBuildScripts::run_once(config, &cargo_ws, progress) { Ok(it) => Ok(it.into_iter()), // io::Error is not Clone? Err(e) => Err(Arc::new(e)), @@ -417,9 +461,11 @@ impl ProjectWorkspace { /// The return type contains the path and whether or not /// the root is a member of the current workspace pub fn to_roots(&self) -> Vec<PackageRoot> { - let mk_sysroot = |sysroot: Option<&Sysroot>| { + let mk_sysroot = |sysroot: Option<&Sysroot>, project_root: Option<&AbsPath>| { sysroot.map(|sysroot| PackageRoot { - is_local: false, + // mark the sysroot as mutable if it is located inside of the project + is_local: project_root + .map_or(false, |project_root| sysroot.src_root().starts_with(project_root)), include: vec![sysroot.src_root().to_path_buf()], exclude: Vec::new(), }) @@ -434,7 +480,7 @@ impl ProjectWorkspace { }) .collect::<FxHashSet<_>>() .into_iter() - .chain(mk_sysroot(sysroot.as_ref())) + .chain(mk_sysroot(sysroot.as_ref(), Some(project.path()))) .collect::<Vec<_>>(), ProjectWorkspace::Cargo { cargo, @@ -484,7 +530,7 @@ impl ProjectWorkspace { } PackageRoot { is_local, include, exclude } }) - .chain(mk_sysroot(sysroot.as_ref())) + .chain(mk_sysroot(sysroot.as_ref(), Some(cargo.workspace_root()))) .chain(rustc.iter().flat_map(|rustc| { rustc.packages().map(move |krate| PackageRoot { is_local: false, @@ -501,7 +547,7 @@ impl ProjectWorkspace { include: vec![detached_file.clone()], exclude: Vec::new(), }) - .chain(mk_sysroot(sysroot.as_ref())) + .chain(mk_sysroot(sysroot.as_ref(), None)) .collect(), } } @@ -538,9 +584,9 @@ impl ProjectWorkspace { load_proc_macro, load, project, - sysroot, + sysroot.as_ref(), extra_env, - None, + Err("rust-project.json projects have no target layout set".into()), ), ProjectWorkspace::Cargo { cargo, @@ -560,10 +606,19 @@ impl ProjectWorkspace { rustc_cfg.clone(), cfg_overrides, build_scripts, - target_layout.as_deref().map(Arc::from), + match target_layout.as_ref() { + Ok(it) => Ok(Arc::from(it.as_str())), + Err(it) => Err(Arc::from(it.as_str())), + }, ), ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => { - detached_files_to_crate_graph(rustc_cfg.clone(), load, files, sysroot, None) + detached_files_to_crate_graph( + rustc_cfg.clone(), + load, + files, + sysroot, + Err("detached file projects have no target layout set".into()), + ) } }; if crate_graph.patch_cfg_if() { @@ -573,6 +628,49 @@ impl ProjectWorkspace { } crate_graph } + + pub fn eq_ignore_build_data(&self, other: &Self) -> bool { + match (self, other) { + ( + Self::Cargo { + cargo, + sysroot, + rustc, + rustc_cfg, + cfg_overrides, + toolchain, + build_scripts: _, + target_layout: _, + }, + Self::Cargo { + cargo: o_cargo, + sysroot: o_sysroot, + rustc: o_rustc, + rustc_cfg: o_rustc_cfg, + cfg_overrides: o_cfg_overrides, + toolchain: o_toolchain, + build_scripts: _, + target_layout: _, + }, + ) => { + cargo == o_cargo + && rustc == o_rustc + && rustc_cfg == o_rustc_cfg + && cfg_overrides == o_cfg_overrides + && toolchain == o_toolchain + && sysroot == o_sysroot + } + ( + Self::Json { project, sysroot, rustc_cfg }, + Self::Json { project: o_project, sysroot: o_sysroot, rustc_cfg: o_rustc_cfg }, + ) => project == o_project && rustc_cfg == o_rustc_cfg && sysroot == o_sysroot, + ( + Self::DetachedFiles { files, sysroot, rustc_cfg }, + Self::DetachedFiles { files: o_files, sysroot: o_sysroot, rustc_cfg: o_rustc_cfg }, + ) => files == o_files && sysroot == o_sysroot && rustc_cfg == o_rustc_cfg, + _ => false, + } + } } fn project_json_to_crate_graph( @@ -580,9 +678,9 @@ fn project_json_to_crate_graph( load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult, load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, project: &ProjectJson, - sysroot: &Option<Sysroot>, + sysroot: Option<&Sysroot>, extra_env: &FxHashMap<String, String>, - target_layout: Option<Arc<str>>, + target_layout: TargetLayoutLoadResult, ) -> CrateGraph { let mut crate_graph = CrateGraph::default(); let sysroot_deps = sysroot.as_ref().map(|sysroot| { @@ -686,7 +784,7 @@ fn cargo_to_crate_graph( rustc_cfg: Vec<CfgFlag>, override_cfg: &CfgOverrides, build_scripts: &WorkspaceBuildScripts, - target_layout: Option<Arc<str>>, + target_layout: TargetLayoutLoadResult, ) -> CrateGraph { let _p = profile::span("cargo_to_crate_graph"); let mut crate_graph = CrateGraph::default(); @@ -852,7 +950,7 @@ fn detached_files_to_crate_graph( load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, detached_files: &[AbsPathBuf], sysroot: &Option<Sysroot>, - target_layout: Option<Arc<str>>, + target_layout: TargetLayoutLoadResult, ) -> CrateGraph { let _p = profile::span("detached_files_to_crate_graph"); let mut crate_graph = CrateGraph::default(); @@ -917,7 +1015,7 @@ fn handle_rustc_crates( cfg_options: &CfgOptions, override_cfg: &CfgOverrides, build_scripts: &WorkspaceBuildScripts, - target_layout: Option<Arc<str>>, + target_layout: TargetLayoutLoadResult, ) { let mut rustc_pkg_crates = FxHashMap::default(); // The root package of the rustc-dev component is rustc_driver, so we match that @@ -1039,7 +1137,7 @@ fn add_target_crate_root( file_id: FileId, cargo_name: &str, is_proc_macro: bool, - target_layout: Option<Arc<str>>, + target_layout: TargetLayoutLoadResult, ) -> CrateId { let edition = pkg.edition; let mut potential_cfg_options = cfg_options.clone(); @@ -1108,7 +1206,7 @@ fn sysroot_to_crate_graph( crate_graph: &mut CrateGraph, sysroot: &Sysroot, rustc_cfg: Vec<CfgFlag>, - target_layout: Option<Arc<str>>, + target_layout: TargetLayoutLoadResult, load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, ) -> (SysrootPublicDeps, Option<CrateId>) { let _p = profile::span("sysroot_to_crate_graph"); @@ -1150,9 +1248,7 @@ fn sysroot_to_crate_graph( let public_deps = SysrootPublicDeps { deps: sysroot .public_deps() - .map(|(name, idx, prelude)| { - (CrateName::new(name).unwrap(), sysroot_crates[&idx], prelude) - }) + .map(|(name, idx, prelude)| (name, sysroot_crates[&idx], prelude)) .collect::<Vec<_>>(), }; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml index 56f14fe18..f0f1900c7 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "rust-analyzer" version = "0.0.0" -authors = ["rust-analyzer Team"] homepage = "https://github.com/rust-analyzer/rust-analyzer" description = "A language server for the Rust programming language" documentation = "https://rust-analyzer.github.io/manual.html" -license = "MIT OR Apache-2.0" autobins = false -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -23,7 +24,7 @@ crossbeam-channel = "0.5.5" dissimilar = "1.0.4" itertools = "0.10.5" scip = "0.1.1" -lsp-types = { version = "=0.93.2", features = ["proposed"] } +lsp-types = { version = "=0.94", features = ["proposed"] } parking_lot = "0.12.1" xflags = "0.3.0" oorandom = "11.1.3" @@ -31,8 +32,8 @@ rustc-hash = "1.1.0" serde = { version = "1.0.137", features = ["derive"] } serde_json = { version = "1.0.81", features = ["preserve_order"] } threadpool = "1.8.1" -rayon = "1.5.3" -num_cpus = "1.13.1" +rayon = "1.6.1" +num_cpus = "1.15.0" mimalloc = { version = "0.1.30", default-features = false, optional = true } lsp-server = { version = "0.7.0", path = "../../lib/lsp-server" } tracing = "0.1.35" @@ -46,26 +47,25 @@ tracing-log = "0.1.3" tracing-tree = "0.2.1" always-assert = "0.1.2" -stdx = { path = "../stdx", version = "0.0.0" } -flycheck = { path = "../flycheck", version = "0.0.0" } -ide = { path = "../ide", version = "0.0.0" } -ide-db = { path = "../ide-db", version = "0.0.0" } -profile = { path = "../profile", version = "0.0.0" } -project-model = { path = "../project-model", version = "0.0.0" } -syntax = { path = "../syntax", version = "0.0.0" } -vfs = { path = "../vfs", version = "0.0.0" } -vfs-notify = { path = "../vfs-notify", version = "0.0.0" } -cfg = { path = "../cfg", version = "0.0.0" } -toolchain = { path = "../toolchain", version = "0.0.0" } -tt = { path = "../tt", version = "0.0.0" } -proc-macro-api = { path = "../proc-macro-api", version = "0.0.0" } - +cfg.workspace = true +flycheck.workspace = true +hir-def.workspace = true +hir-ty.workspace = true +hir.workspace = true +ide-db.workspace = true # This should only be used in CLI -ide-ssr = { path = "../ide-ssr", version = "0.0.0" } -hir = { path = "../hir", version = "0.0.0" } -hir-def = { path = "../hir-def", version = "0.0.0" } -hir-ty = { path = "../hir-ty", version = "0.0.0" } -proc-macro-srv = { path = "../proc-macro-srv", version = "0.0.0" } +ide-ssr.workspace = true +ide.workspace = true +proc-macro-api.workspace = true +proc-macro-srv.workspace = true +profile.workspace = true +project-model.workspace = true +stdx.workspace = true +syntax.workspace = true +toolchain.workspace = true +tt.workspace = true +vfs-notify.workspace = true +vfs.workspace = true [target.'cfg(windows)'.dependencies] winapi = "0.3.9" @@ -78,9 +78,9 @@ expect-test = "1.4.0" jod-thread = "0.1.2" xshell = "0.2.2" -test-utils = { path = "../test-utils" } -sourcegen = { path = "../sourcegen" } -mbe = { path = "../mbe" } +test-utils.workspace = true +sourcegen.workspace = true +mbe.workspace = true [features] jemalloc = ["jemallocator", "profile/jemalloc"] diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs index 53710749d..4de022b6e 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs @@ -10,7 +10,6 @@ mod rustc_wrapper; use std::{env, fs, path::Path, process}; use lsp_server::Connection; -use project_model::ProjectManifest; use rust_analyzer::{cli::flags, config::Config, from_json, Result}; use vfs::AbsPathBuf; @@ -168,7 +167,18 @@ fn run_server() -> Result<()> { } }; - let mut config = Config::new(root_path, initialize_params.capabilities); + let workspace_roots = initialize_params + .workspace_folders + .map(|workspaces| { + workspaces + .into_iter() + .filter_map(|it| it.uri.to_file_path().ok()) + .filter_map(|it| AbsPathBuf::try_from(it).ok()) + .collect::<Vec<_>>() + }) + .filter(|workspaces| !workspaces.is_empty()) + .unwrap_or_else(|| vec![root_path.clone()]); + let mut config = Config::new(root_path, initialize_params.capabilities, workspace_roots); if let Some(json) = initialize_params.initialization_options { if let Err(e) = config.update(json) { use lsp_types::{ @@ -183,8 +193,6 @@ fn run_server() -> Result<()> { } } - config.client_specific_adjustments(&initialize_params.client_info); - let server_capabilities = rust_analyzer::server_capabilities(&config); let initialize_result = lsp_types::InitializeResult { @@ -204,25 +212,8 @@ fn run_server() -> Result<()> { tracing::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default()); } - if config.linked_projects().is_empty() && config.detached_files().is_empty() { - let workspace_roots = initialize_params - .workspace_folders - .map(|workspaces| { - workspaces - .into_iter() - .filter_map(|it| it.uri.to_file_path().ok()) - .filter_map(|it| AbsPathBuf::try_from(it).ok()) - .collect::<Vec<_>>() - }) - .filter(|workspaces| !workspaces.is_empty()) - .unwrap_or_else(|| vec![config.root_path().clone()]); - - let discovered = ProjectManifest::discover_all(&workspace_roots); - tracing::info!("discovered projects: {:?}", discovered); - if discovered.is_empty() { - tracing::error!("failed to find any projects in {:?}", workspace_roots); - } - config.discovered_projects = Some(discovered); + if !config.has_linked_projects() && config.detached_files().is_empty() { + config.rediscover_workspaces(); } rust_analyzer::main_loop(config, connection)?; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/caps.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/caps.rs index 122d2e6ff..3628670ac 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/caps.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/caps.rs @@ -1,4 +1,5 @@ //! Advertises the capabilities of the LSP Server. +use ide_db::line_index::WideEncoding; use lsp_types::{ CallHierarchyServerCapability, ClientCapabilities, CodeActionKind, CodeActionOptions, CodeActionProviderCapability, CodeLensOptions, CompletionOptions, @@ -10,21 +11,25 @@ use lsp_types::{ SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions, ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability, WorkDoneProgressOptions, - WorkspaceFileOperationsServerCapabilities, WorkspaceServerCapabilities, + WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities, + WorkspaceServerCapabilities, }; use serde_json::json; use crate::config::{Config, RustfmtConfig}; -use crate::lsp_ext::supports_utf8; +use crate::line_index::PositionEncoding; +use crate::lsp_ext::negotiated_encoding; use crate::semantic_tokens; pub fn server_capabilities(config: &Config) -> ServerCapabilities { ServerCapabilities { - position_encoding: if supports_utf8(config.caps()) { - Some(PositionEncodingKind::UTF8) - } else { - None - }, + position_encoding: Some(match negotiated_encoding(config.caps()) { + PositionEncoding::Utf8 => PositionEncodingKind::UTF8, + PositionEncoding::Wide(wide) => match wide { + WideEncoding::Utf16 => PositionEncodingKind::UTF16, + WideEncoding::Utf32 => PositionEncodingKind::UTF32, + }, + }), text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions { open_close: Some(true), change: Some(TextDocumentSyncKind::INCREMENTAL), @@ -80,7 +85,10 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { color_provider: None, execute_command_provider: None, workspace: Some(WorkspaceServerCapabilities { - workspace_folders: None, + workspace_folders: Some(WorkspaceFoldersServerCapabilities { + supported: Some(true), + change_notifications: Some(OneOf::Left(true)), + }), file_operations: Some(WorkspaceFileOperationsServerCapabilities { did_create: None, will_create: None, @@ -130,6 +138,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { resolve_provider: Some(true), }, ))), + inline_value_provider: None, experimental: Some(json!({ "externalDocs": true, "hoverRange": true, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs index 053db5fc5..93297faa6 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -15,7 +15,7 @@ use hir_def::{ expr::ExprId, FunctionId, }; -use hir_ty::{TyExt, TypeWalk}; +use hir_ty::{Interner, TyExt, TypeFlags}; use ide::{Analysis, AnalysisHost, LineCol, RootDatabase}; use ide_db::base_db::{ salsa::{self, debug::DebugQueryTable, ParallelDatabase}, @@ -33,7 +33,7 @@ use vfs::{AbsPathBuf, Vfs, VfsPath}; use crate::cli::{ flags::{self, OutputFormat}, - load_cargo::{load_workspace, LoadCargoConfig}, + load_cargo::{load_workspace, LoadCargoConfig, ProcMacroServerChoice}, print_memory_usage, progress_report::ProgressReport, report_metric, Result, Verbosity, @@ -59,11 +59,6 @@ impl flags::AnalysisStats { true => None, false => Some(RustcSource::Discover), }; - let load_cargo_config = LoadCargoConfig { - load_out_dirs_from_check: !self.disable_build_scripts, - with_proc_macro: !self.disable_proc_macros, - prefill_caches: false, - }; let no_progress = &|_| (); let mut db_load_sw = self.stop_watch(); @@ -73,6 +68,11 @@ impl flags::AnalysisStats { let mut workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?; let metadata_time = db_load_sw.elapsed(); + let load_cargo_config = LoadCargoConfig { + load_out_dirs_from_check: !self.disable_build_scripts, + with_proc_macro_server: ProcMacroServerChoice::Sysroot, + prefill_caches: false, + }; let build_scripts_time = if self.disable_build_scripts { None @@ -280,12 +280,8 @@ impl flags::AnalysisStats { } true } else { - let mut is_partially_unknown = false; - ty.walk(&mut |ty| { - if ty.is_unknown() { - is_partially_unknown = true; - } - }); + let is_partially_unknown = + ty.data(Interner).flags.contains(TypeFlags::HAS_ERROR); if is_partially_unknown { num_exprs_partially_unknown += 1; } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs index fd5b3ce61..ff821be53 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs @@ -9,7 +9,7 @@ use ide_db::base_db::SourceDatabaseExt; use crate::cli::{ flags, - load_cargo::{load_workspace_at, LoadCargoConfig}, + load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice}, }; impl flags::Diagnostics { @@ -17,7 +17,7 @@ impl flags::Diagnostics { let cargo_config = Default::default(); let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: !self.disable_build_scripts, - with_proc_macro: !self.disable_proc_macros, + with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, }; let (host, _vfs, _proc_macro) = diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/load_cargo.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/load_cargo.rs index 762d7d3a1..5a958d963 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/load_cargo.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/load_cargo.rs @@ -1,6 +1,6 @@ //! Loads a Cargo project into a static instance of analysis, without support //! for incorporating changes. -use std::{path::Path, sync::Arc}; +use std::{convert::identity, path::Path, sync::Arc}; use anyhow::Result; use crossbeam_channel::{unbounded, Receiver}; @@ -17,10 +17,17 @@ use crate::reload::{load_proc_macro, ProjectFolders, SourceRootConfig}; // what otherwise would be `pub(crate)` has to be `pub` here instead. pub struct LoadCargoConfig { pub load_out_dirs_from_check: bool, - pub with_proc_macro: bool, + pub with_proc_macro_server: ProcMacroServerChoice, pub prefill_caches: bool, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ProcMacroServerChoice { + Sysroot, + Explicit(AbsPathBuf, Vec<String>), + None, +} + // Note: Since this function is used by external tools that use rust-analyzer as a library // what otherwise would be `pub(crate)` has to be `pub` here instead. pub fn load_workspace_at( @@ -59,15 +66,17 @@ pub fn load_workspace( Box::new(loader) }; - let proc_macro_client = if load_config.with_proc_macro { - let (server_path, args): (_, &[_]) = match ws.find_sysroot_proc_macro_srv() { - Some(server_path) => (server_path, &[]), - None => (AbsPathBuf::assert(std::env::current_exe()?), &["proc-macro"]), - }; - - ProcMacroServer::spawn(server_path, args).map_err(|e| e.to_string()) - } else { - Err("proc macro server disabled".to_owned()) + let proc_macro_client = match &load_config.with_proc_macro_server { + ProcMacroServerChoice::Sysroot => ws + .find_sysroot_proc_macro_srv() + .ok_or_else(|| "failed to find sysroot proc-macro server".to_owned()) + .and_then(|it| { + ProcMacroServer::spawn(it, identity::<&[&str]>(&[])).map_err(|e| e.to_string()) + }), + ProcMacroServerChoice::Explicit(path, args) => { + ProcMacroServer::spawn(path.clone(), args).map_err(|e| e.to_string()) + } + ProcMacroServerChoice::None => Err("proc macro server disabled".to_owned()), }; let crate_graph = ws.to_crate_graph( @@ -157,7 +166,7 @@ mod tests { let cargo_config = CargoConfig::default(); let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: false, - with_proc_macro: false, + with_proc_macro_server: ProcMacroServerChoice::None, prefill_caches: false, }; let (host, _vfs, _proc_macro) = diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs index af8356d04..3fc1aa4ea 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs @@ -11,10 +11,12 @@ use ide::{ use ide_db::LineIndexDatabase; use ide_db::base_db::salsa::{self, ParallelDatabase}; +use ide_db::line_index::WideEncoding; use lsp_types::{self, lsif}; use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace}; use vfs::{AbsPathBuf, Vfs}; +use crate::cli::load_cargo::ProcMacroServerChoice; use crate::cli::{ flags, load_cargo::{load_workspace, LoadCargoConfig}, @@ -126,7 +128,7 @@ impl LsifManager<'_> { let line_index = self.db.line_index(file_id); let line_index = LineIndex { index: line_index, - encoding: PositionEncoding::Utf16, + encoding: PositionEncoding::Wide(WideEncoding::Utf16), endings: LineEndings::Unix, }; let range_id = self.add_vertex(lsif::Vertex::Range { @@ -248,7 +250,7 @@ impl LsifManager<'_> { let line_index = self.db.line_index(file_id); let line_index = LineIndex { index: line_index, - encoding: PositionEncoding::Utf16, + encoding: PositionEncoding::Wide(WideEncoding::Utf16), endings: LineEndings::Unix, }; let result = folds @@ -291,7 +293,7 @@ impl flags::Lsif { let no_progress = &|_| (); let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: true, - with_proc_macro: true, + with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, }; let path = AbsPathBuf::assert(env::current_dir()?.join(&self.path)); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs index b050d1e95..9a04fbea7 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs @@ -5,7 +5,10 @@ use std::{ time::Instant, }; -use crate::line_index::{LineEndings, LineIndex, PositionEncoding}; +use crate::{ + cli::load_cargo::ProcMacroServerChoice, + line_index::{LineEndings, LineIndex, PositionEncoding}, +}; use hir::Name; use ide::{ LineCol, MonikerDescriptorKind, StaticIndex, StaticIndexedFile, TextRange, TokenId, @@ -31,7 +34,7 @@ impl flags::Scip { let no_progress = &|s| (eprintln!("rust-analyzer: Loading {s}")); let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: true, - with_proc_macro: true, + with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: true, }; let path = vfs::AbsPathBuf::assert(env::current_dir()?.join(&self.path)); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs index 84c489171..3552f840a 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs @@ -5,7 +5,7 @@ use project_model::CargoConfig; use crate::cli::{ flags, - load_cargo::{load_workspace_at, LoadCargoConfig}, + load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice}, Result, }; @@ -15,7 +15,7 @@ impl flags::Ssr { let cargo_config = CargoConfig::default(); let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: true, - with_proc_macro: true, + with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, }; let (host, vfs, _proc_macro) = load_workspace_at( @@ -51,7 +51,7 @@ impl flags::Search { let cargo_config = CargoConfig::default(); let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: true, - with_proc_macro: true, + with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, }; let (host, _vfs, _proc_macro) = load_workspace_at( diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index b0afbdc9a..f609a50a0 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -20,7 +20,7 @@ use ide_db::{ SnippetCap, }; use itertools::Itertools; -use lsp_types::{ClientCapabilities, ClientInfo, MarkupKind}; +use lsp_types::{ClientCapabilities, MarkupKind}; use project_model::{ CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource, UnsetTestCrates, @@ -33,7 +33,7 @@ use crate::{ caps::completion_item_edit_resolve, diagnostics::DiagnosticsMapConfig, line_index::PositionEncoding, - lsp_ext::{self, supports_utf8, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope}, + lsp_ext::{self, negotiated_encoding, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope}, }; mod patch_old_style; @@ -117,6 +117,11 @@ config_data! { /// /// This option does not take effect until rust-analyzer is restarted. cargo_sysroot: Option<String> = "\"discover\"", + /// Relative path to the sysroot library sources. If left unset, this will default to + /// `{cargo.sysroot}/lib/rustlib/src/rust/library`. + /// + /// This option does not take effect until rust-analyzer is restarted. + cargo_sysrootSrc: Option<String> = "null", /// Compilation target override (target triple). // FIXME(@poliorcetics): move to multiple targets here too, but this will need more work // than `checkOnSave_target` @@ -195,6 +200,8 @@ config_data! { completion_autoself_enable: bool = "true", /// Whether to add parenthesis and argument snippets when completing function. completion_callable_snippets: CallableCompletionDef = "\"fill_arguments\"", + /// Maximum number of completions to return. If `None`, the limit is infinite. + completion_limit: Option<usize> = "null", /// Whether to show postfix snippets like `dbg`, `if`, `not`, etc. completion_postfix_enable: bool = "true", /// Enables completions of private items and fields that are defined in the current workspace even if they are not visible at the current position. @@ -342,8 +349,6 @@ config_data! { inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"", /// Whether to prefer using parameter names as the name for elided lifetime hints if possible. inlayHints_lifetimeElisionHints_useParameterNames: bool = "false", - /// Whether to use location links for parts of type mentioned in inlay hints. - inlayHints_locationLinks: bool = "true", /// Maximum length for inlay hints. Set to null to have an unlimited length. inlayHints_maxLength: Option<usize> = "25", /// Whether to show function parameter name inlay hints at the call @@ -521,6 +526,7 @@ impl Default for ConfigData { #[derive(Debug, Clone)] pub struct Config { pub discovered_projects: Option<Vec<ProjectManifest>>, + pub workspace_roots: Vec<AbsPathBuf>, caps: lsp_types::ClientCapabilities, root_path: AbsPathBuf, data: ConfigData, @@ -717,7 +723,11 @@ impl fmt::Display for ConfigUpdateError { } impl Config { - pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self { + pub fn new( + root_path: AbsPathBuf, + caps: ClientCapabilities, + workspace_roots: Vec<AbsPathBuf>, + ) -> Self { Config { caps, data: ConfigData::default(), @@ -725,20 +735,17 @@ impl Config { discovered_projects: None, root_path, snippets: Default::default(), + workspace_roots, } } - pub fn client_specific_adjustments(&mut self, client_info: &Option<ClientInfo>) { - // FIXME: remove this when we drop support for vscode 1.65 and below - if let Some(client) = client_info { - if client.name.contains("Code") || client.name.contains("Codium") { - if let Some(version) = &client.version { - if version.as_str() < "1.76" { - self.data.inlayHints_locationLinks = false; - } - } - } + pub fn rediscover_workspaces(&mut self) { + let discovered = ProjectManifest::discover_all(&self.workspace_roots); + tracing::info!("discovered projects: {:?}", discovered); + if discovered.is_empty() { + tracing::error!("failed to find any projects in {:?}", &self.workspace_roots); } + self.discovered_projects = Some(discovered); } pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> { @@ -837,6 +844,9 @@ macro_rules! try_or_def { } impl Config { + pub fn has_linked_projects(&self) -> bool { + !self.data.linkedProjects.is_empty() + } pub fn linked_projects(&self) -> Vec<LinkedProject> { match self.data.linkedProjects.as_slice() { [] => match self.discovered_projects.as_ref() { @@ -989,11 +999,7 @@ impl Config { } pub fn position_encoding(&self) -> PositionEncoding { - if supports_utf8(&self.caps) { - PositionEncoding::Utf8 - } else { - PositionEncoding::Utf16 - } + negotiated_encoding(&self.caps) } fn experimental(&self, index: &'static str) -> bool { @@ -1004,6 +1010,10 @@ impl Config { self.experimental("codeActionGroup") } + pub fn open_server_logs(&self) -> bool { + self.experimental("openServerLogs") + } + pub fn server_status_notification(&self) -> bool { self.experimental("serverStatusNotification") } @@ -1044,7 +1054,7 @@ impl Config { &self.data.cargo_extraEnv } - pub fn check_on_save_extra_env(&self) -> FxHashMap<String, String> { + pub fn check_extra_env(&self) -> FxHashMap<String, String> { let mut extra_env = self.data.cargo_extraEnv.clone(); extra_env.extend(self.data.check_extraEnv.clone()); extra_env @@ -1114,6 +1124,8 @@ impl Config { RustcSource::Path(self.root_path.join(sysroot)) } }); + let sysroot_src = + self.data.cargo_sysrootSrc.as_ref().map(|sysroot| self.root_path.join(sysroot)); CargoConfig { features: match &self.data.cargo_features { @@ -1125,6 +1137,7 @@ impl Config { }, target: self.data.cargo_target.clone(), sysroot, + sysroot_src, rustc_source, unset_test_crates: UnsetTestCrates::Only(self.data.cargo_unsetTest.clone()), wrap_rustc_in_build_scripts: self.data.cargo_buildScripts_useRustcWrapper, @@ -1165,7 +1178,7 @@ impl Config { FlycheckConfig::CustomCommand { command, args, - extra_env: self.check_on_save_extra_env(), + extra_env: self.check_extra_env(), invocation_strategy: match self.data.check_invocationStrategy { InvocationStrategy::Once => flycheck::InvocationStrategy::Once, InvocationStrategy::PerWorkspace => { @@ -1210,7 +1223,7 @@ impl Config { CargoFeaturesDef::Selected(it) => it, }, extra_args: self.data.check_extraArgs.clone(), - extra_env: self.check_on_save_extra_env(), + extra_env: self.check_extra_env(), ansi_color_output: self.color_diagnostic_output(), }, } @@ -1229,7 +1242,6 @@ impl Config { pub fn inlay_hints(&self) -> InlayHintsConfig { InlayHintsConfig { - location_links: self.data.inlayHints_locationLinks, render_colons: self.data.inlayHints_renderColons, type_hints: self.data.inlayHints_typeHints_enable, parameter_hints: self.data.inlayHints_parameterHints_enable, @@ -1329,6 +1341,7 @@ impl Config { .snippet_support? )), snippets: self.snippets.clone(), + limit: self.data.completion_limit, } } @@ -1409,7 +1422,8 @@ impl Config { pub fn hover(&self) -> HoverConfig { HoverConfig { links_in_hover: self.data.hover_links_enable, - documentation: self.data.hover_documentation_enable.then(|| { + documentation: self.data.hover_documentation_enable, + format: { let is_markdown = try_or_def!(self .caps .text_document @@ -1425,7 +1439,7 @@ impl Config { } else { HoverDocFormat::PlainText } - }), + }, keywords: self.data.hover_documentation_keywords_enable, } } @@ -1454,6 +1468,10 @@ impl Config { try_or_def!(self.caps.workspace.as_ref()?.code_lens.as_ref()?.refresh_support?) } + pub fn inlay_hints_refresh(&self) -> bool { + try_or_def!(self.caps.workspace.as_ref()?.inlay_hint.as_ref()?.refresh_support?) + } + pub fn insert_replace_support(&self) -> bool { try_or_def!( self.caps diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config/patch_old_style.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config/patch_old_style.rs index de6ac946a..73d2ed329 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config/patch_old_style.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config/patch_old_style.rs @@ -114,16 +114,18 @@ pub(super) fn patch_json_for_outdated_configs(json: &mut Value) { } // completion_addCallArgumentSnippets completion_addCallParenthesis -> completion_callable_snippets - let res = match ( - copy.pointer("/completion/addCallArgumentSnippets"), - copy.pointer("/completion/addCallParenthesis"), - ) { - (Some(Value::Bool(true)), Some(Value::Bool(true))) => json!("fill_arguments"), - (_, Some(Value::Bool(true))) => json!("add_parentheses"), - (Some(Value::Bool(false)), Some(Value::Bool(false))) => json!("none"), - (_, _) => return, - }; - merge(json, json!({ "completion": { "callable": {"snippets": res }} })); + 'completion: { + let res = match ( + copy.pointer("/completion/addCallArgumentSnippets"), + copy.pointer("/completion/addCallParenthesis"), + ) { + (Some(Value::Bool(true)), Some(Value::Bool(true))) => json!("fill_arguments"), + (_, Some(Value::Bool(true))) => json!("add_parentheses"), + (Some(Value::Bool(false)), Some(Value::Bool(false))) => json!("none"), + (_, _) => break 'completion, + }; + merge(json, json!({ "completion": { "callable": {"snippets": res }} })); + } // We need to do this due to the checkOnSave_enable -> checkOnSave change, as that key now can either be an object or a bool // checkOnSave_* -> check_* @@ -146,3 +148,23 @@ fn merge(dst: &mut Value, src: Value) { (dst, src) => *dst = src, } } + +#[test] +fn check_on_save_patching() { + let mut json = json!({ "checkOnSave": { "overrideCommand": "foo" }}); + patch_json_for_outdated_configs(&mut json); + assert_eq!( + json, + json!({ "checkOnSave": { "overrideCommand": "foo" }, "check": { "overrideCommand": "foo" }}) + ); +} + +#[test] +fn check_on_save_patching_enable() { + let mut json = json!({ "checkOnSave": { "enable": true, "overrideCommand": "foo" }}); + patch_json_for_outdated_configs(&mut json); + assert_eq!( + json, + json!({ "checkOnSave": true, "check": { "enable": true, "overrideCommand": "foo" }}) + ); +} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs index acb416a06..415fa4e02 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan}; +use ide_db::line_index::WideEncoding; use itertools::Itertools; use stdx::format_to; use vfs::{AbsPath, AbsPathBuf}; @@ -95,7 +96,8 @@ fn position( let mut char_offset = 0; let len_func = match position_encoding { PositionEncoding::Utf8 => char::len_utf8, - PositionEncoding::Utf16 => char::len_utf16, + PositionEncoding::Wide(WideEncoding::Utf16) => char::len_utf16, + PositionEncoding::Wide(WideEncoding::Utf32) => |_| 1, }; for c in line.text.chars() { char_offset += 1; @@ -534,7 +536,7 @@ mod tests { let (sender, _) = crossbeam_channel::unbounded(); let state = GlobalState::new( sender, - Config::new(workspace_root.to_path_buf(), ClientCapabilities::default()), + Config::new(workspace_root.to_path_buf(), ClientCapabilities::default(), Vec::new()), ); let snap = state.snapshot(); let mut actual = map_rust_diagnostic_to_lsp(&config, &diagnostic, workspace_root, &snap); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/from_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/from_proto.rs index 2dbb14fcd..50af38cd6 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/from_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/from_proto.rs @@ -1,7 +1,10 @@ //! Conversion lsp_types types to rust-analyzer specific ones. use anyhow::format_err; -use ide::{Annotation, AnnotationKind, AssistKind, LineCol, LineColUtf16}; -use ide_db::base_db::{FileId, FilePosition, FileRange}; +use ide::{Annotation, AnnotationKind, AssistKind, LineCol}; +use ide_db::{ + base_db::{FileId, FilePosition, FileRange}, + line_index::WideLineCol, +}; use syntax::{TextRange, TextSize}; use vfs::AbsPathBuf; @@ -26,9 +29,9 @@ pub(crate) fn vfs_path(url: &lsp_types::Url) -> Result<vfs::VfsPath> { pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> Result<TextSize> { let line_col = match line_index.encoding { PositionEncoding::Utf8 => LineCol { line: position.line, col: position.character }, - PositionEncoding::Utf16 => { - let line_col = LineColUtf16 { line: position.line, col: position.character }; - line_index.index.to_utf8(line_col) + PositionEncoding::Wide(enc) => { + let line_col = WideLineCol { line: position.line, col: position.character }; + line_index.index.to_utf8(enc, line_col) } }; let text_size = diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs index c6f4e9ce0..aca6c9235 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs @@ -179,10 +179,9 @@ impl GlobalState { pub(crate) fn process_changes(&mut self) -> bool { let _p = profile::span("GlobalState::process_changes"); - // A file was added or deleted - let mut has_structure_changes = false; let mut workspace_structure_change = None; + let mut file_changes = FxHashMap::default(); let (change, changed_files) = { let mut change = Change::new(); let (vfs, line_endings_map) = &mut *self.vfs.write(); @@ -191,43 +190,56 @@ impl GlobalState { return false; } - // important: this needs to be a stable sort, the order between changes is relevant - // for the same file ids - changed_files.sort_by_key(|file| file.file_id); - // We need to fix up the changed events a bit, if we have a create or modify for a file - // id that is followed by a delete we actually no longer observe the file text from the - // create or modify which may cause problems later on - changed_files.dedup_by(|a, b| { + // We need to fix up the changed events a bit. If we have a create or modify for a file + // id that is followed by a delete we actually skip observing the file text from the + // earlier event, to avoid problems later on. + for changed_file in &changed_files { use vfs::ChangeKind::*; - if a.file_id != b.file_id { - return false; - } + file_changes + .entry(changed_file.file_id) + .and_modify(|(change, just_created)| { + // None -> Delete => keep + // Create -> Delete => collapse + // + match (change, just_created, changed_file.change_kind) { + // latter `Delete` wins + (change, _, Delete) => *change = Delete, + // merge `Create` with `Create` or `Modify` + (Create, _, Create | Modify) => {} + // collapse identical `Modify`es + (Modify, _, Modify) => {} + // equivalent to `Modify` + (change @ Delete, just_created, Create) => { + *change = Modify; + *just_created = true; + } + // shouldn't occur, but collapse into `Create` + (change @ Delete, just_created, Modify) => { + *change = Create; + *just_created = true; + } + // shouldn't occur, but collapse into `Modify` + (Modify, _, Create) => {} + } + }) + .or_insert(( + changed_file.change_kind, + matches!(changed_file.change_kind, Create), + )); + } - match (a.change_kind, b.change_kind) { - // duplicate can be merged - (Create, Create) | (Modify, Modify) | (Delete, Delete) => true, - // just leave the create, modify is irrelevant - (Create, Modify) => { - std::mem::swap(a, b); - true - } - // modify becomes irrelevant if the file is deleted - (Modify, Delete) => true, - // we should fully remove this occurrence, - // but leaving just a delete works as well - (Create, Delete) => true, - // this is equivalent to a modify - (Delete, Create) => { - a.change_kind = Modify; - true - } - // can't really occur - (Modify, Create) => false, - (Delete, Modify) => false, - } - }); + changed_files.extend( + file_changes + .into_iter() + .filter(|(_, (change_kind, just_created))| { + !matches!((change_kind, just_created), (vfs::ChangeKind::Delete, true)) + }) + .map(|(file_id, (change_kind, _))| vfs::ChangedFile { file_id, change_kind }), + ); + // A file was added or deleted + let mut has_structure_changes = false; for file in &changed_files { if let Some(path) = vfs.file_path(file.file_id).as_path() { let path = path.to_path_buf(); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs index 59bdd3061..4e08bd0a7 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs @@ -29,7 +29,6 @@ use project_model::{ManifestPath, ProjectWorkspace, TargetKind}; use serde_json::json; use stdx::{format_to, never}; use syntax::{algo, ast, AstNode, TextRange, TextSize}; -use tracing::error; use vfs::AbsPathBuf; use crate::{ @@ -937,8 +936,7 @@ pub(crate) fn handle_hover( let line_index = snap.file_line_index(file_range.file_id)?; let range = to_proto::range(&line_index, info.range); - let markup_kind = - snap.config.hover().documentation.map_or(ide::HoverDocFormat::Markdown, |kind| kind); + let markup_kind = snap.config.hover().format; let hover = lsp_ext::Hover { hover: lsp_types::Hover { contents: HoverContents::Markup(to_proto::markup_content( @@ -1360,55 +1358,10 @@ pub(crate) fn handle_inlay_hints( } pub(crate) fn handle_inlay_hints_resolve( - snap: GlobalStateSnapshot, - mut hint: InlayHint, + _snap: GlobalStateSnapshot, + hint: InlayHint, ) -> Result<InlayHint> { let _p = profile::span("handle_inlay_hints_resolve"); - let data = match hint.data.take() { - Some(it) => it, - None => return Ok(hint), - }; - - let resolve_data: lsp_ext::InlayHintResolveData = serde_json::from_value(data)?; - - match snap.url_file_version(&resolve_data.text_document.uri) { - Some(version) if version == resolve_data.text_document.version => {} - Some(version) => { - error!( - "attempted inlayHints/resolve of '{}' at version {} while server version is {}", - resolve_data.text_document.uri, resolve_data.text_document.version, version, - ); - return Ok(hint); - } - None => { - error!( - "attempted inlayHints/resolve of unknown file '{}' at version {}", - resolve_data.text_document.uri, resolve_data.text_document.version, - ); - return Ok(hint); - } - } - let file_range = from_proto::file_range_uri( - &snap, - &resolve_data.text_document.uri, - match resolve_data.position { - PositionOrRange::Position(pos) => Range::new(pos, pos), - PositionOrRange::Range(range) => range, - }, - )?; - let info = match snap.analysis.hover(&snap.config.hover(), file_range)? { - None => return Ok(hint), - Some(info) => info, - }; - - let markup_kind = - snap.config.hover().documentation.map_or(ide::HoverDocFormat::Markdown, |kind| kind); - - // FIXME: hover actions? - hint.tooltip = Some(lsp_types::InlayHintTooltip::MarkupContent(to_proto::markup_content( - info.info.markup, - markup_kind, - ))); Ok(hint) } @@ -1516,7 +1469,8 @@ pub(crate) fn handle_semantic_tokens_full( let mut highlight_config = snap.config.highlighting_config(); // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet. - highlight_config.syntactic_name_ref_highlighting = !snap.proc_macros_loaded; + highlight_config.syntactic_name_ref_highlighting = + snap.workspaces.is_empty() || !snap.proc_macros_loaded; let highlights = snap.analysis.highlight(highlight_config, file_id)?; let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); @@ -1539,7 +1493,8 @@ pub(crate) fn handle_semantic_tokens_full_delta( let mut highlight_config = snap.config.highlighting_config(); // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet. - highlight_config.syntactic_name_ref_highlighting = !snap.proc_macros_loaded; + highlight_config.syntactic_name_ref_highlighting = + snap.workspaces.is_empty() || !snap.proc_macros_loaded; let highlights = snap.analysis.highlight(highlight_config, file_id)?; let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); @@ -1570,7 +1525,12 @@ pub(crate) fn handle_semantic_tokens_range( let text = snap.analysis.file_text(frange.file_id)?; let line_index = snap.file_line_index(frange.file_id)?; - let highlights = snap.analysis.highlight_range(snap.config.highlighting_config(), frange)?; + let mut highlight_config = snap.config.highlighting_config(); + // Avoid flashing a bunch of unresolved references when the proc-macro servers haven't been spawned yet. + highlight_config.syntactic_name_ref_highlighting = + snap.workspaces.is_empty() || !snap.proc_macros_loaded; + + let highlights = snap.analysis.highlight_range(highlight_config, frange)?; let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); Ok(Some(semantic_tokens.into())) } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs index 405d261db..e8912b907 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -21,7 +21,7 @@ use project_model::CargoConfig; use test_utils::project_root; use vfs::{AbsPathBuf, VfsPath}; -use crate::cli::load_cargo::{load_workspace_at, LoadCargoConfig}; +use crate::cli::load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice}; #[test] fn integrated_highlighting_benchmark() { @@ -36,7 +36,7 @@ fn integrated_highlighting_benchmark() { let cargo_config = CargoConfig::default(); let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: true, - with_proc_macro: false, + with_proc_macro_server: ProcMacroServerChoice::None, prefill_caches: false, }; @@ -90,7 +90,7 @@ fn integrated_completion_benchmark() { let cargo_config = CargoConfig::default(); let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: true, - with_proc_macro: false, + with_proc_macro_server: ProcMacroServerChoice::None, prefill_caches: true, }; @@ -146,6 +146,7 @@ fn integrated_completion_benchmark() { }, snippets: Vec::new(), prefer_no_std: false, + limit: None, }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; @@ -184,6 +185,7 @@ fn integrated_completion_benchmark() { }, snippets: Vec::new(), prefer_no_std: false, + limit: None, }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/line_index.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/line_index.rs index 2945dba12..791cd931d 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/line_index.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/line_index.rs @@ -7,9 +7,12 @@ use std::sync::Arc; +use ide_db::line_index::WideEncoding; + +#[derive(Clone, Copy)] pub enum PositionEncoding { Utf8, - Utf16, + Wide(WideEncoding), } pub(crate) struct LineIndex { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs index 65620b420..e33589cc5 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs @@ -2,14 +2,17 @@ use std::{collections::HashMap, path::PathBuf}; +use ide_db::line_index::WideEncoding; use lsp_types::request::Request; +use lsp_types::PositionEncodingKind; use lsp_types::{ notification::Notification, CodeActionKind, DocumentOnTypeFormattingParams, PartialResultParams, Position, Range, TextDocumentIdentifier, WorkDoneProgressParams, }; -use lsp_types::{PositionEncodingKind, VersionedTextDocumentIdentifier}; use serde::{Deserialize, Serialize}; +use crate::line_index::PositionEncoding; + pub enum AnalyzerStatus {} impl Request for AnalyzerStatus { @@ -151,6 +154,13 @@ impl Notification for ClearFlycheck { const METHOD: &'static str = "rust-analyzer/clearFlycheck"; } +pub enum OpenServerLogs {} + +impl Notification for OpenServerLogs { + type Params = (); + const METHOD: &'static str = "rust-analyzer/openServerLogs"; +} + #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct RunFlycheckParams { @@ -474,16 +484,22 @@ pub(crate) enum CodeLensResolveData { References(lsp_types::TextDocumentPositionParams), } -pub fn supports_utf8(caps: &lsp_types::ClientCapabilities) -> bool { - match &caps.general { - Some(general) => general - .position_encodings - .as_deref() - .unwrap_or_default() - .iter() - .any(|it| it == &PositionEncodingKind::UTF8), - _ => false, +pub fn negotiated_encoding(caps: &lsp_types::ClientCapabilities) -> PositionEncoding { + let client_encodings = match &caps.general { + Some(general) => general.position_encodings.as_deref().unwrap_or_default(), + None => &[], + }; + + for enc in client_encodings { + if enc == &PositionEncodingKind::UTF8 { + return PositionEncoding::Utf8; + } else if enc == &PositionEncodingKind::UTF32 { + return PositionEncoding::Wide(WideEncoding::Utf32); + } + // NB: intentionally prefer just about anything else to utf-16. } + + PositionEncoding::Wide(WideEncoding::Utf16) } pub enum MoveItem {} @@ -568,10 +584,7 @@ pub struct CompletionResolveData { } #[derive(Debug, Serialize, Deserialize)] -pub struct InlayHintResolveData { - pub text_document: VersionedTextDocumentIdentifier, - pub position: PositionOrRange, -} +pub struct InlayHintResolveData {} #[derive(Debug, Serialize, Deserialize)] pub struct CompletionImport { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_utils.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_utils.rs index dcaee9285..30f1c53c1 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_utils.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_utils.rs @@ -2,12 +2,13 @@ use std::{mem, ops::Range, sync::Arc}; use lsp_server::Notification; +use lsp_types::request::Request; use crate::{ from_proto, global_state::GlobalState, line_index::{LineEndings, LineIndex, PositionEncoding}, - LspError, + lsp_ext, LspError, }; pub(crate) fn invalid_params_error(message: String) -> LspError { @@ -46,20 +47,47 @@ impl GlobalState { /// If `additional_info` is [`Some`], appends a note to the notification telling to check the logs. /// This will always log `message` + `additional_info` to the server's error log. pub(crate) fn show_and_log_error(&mut self, message: String, additional_info: Option<String>) { - let mut message = message; match additional_info { Some(additional_info) => { - tracing::error!("{}\n\n{}", &message, &additional_info); - if tracing::enabled!(tracing::Level::ERROR) { - message.push_str("\n\nCheck the server logs for additional info."); + tracing::error!("{}:\n{}", &message, &additional_info); + match self.config.open_server_logs() && tracing::enabled!(tracing::Level::ERROR) { + true => self.send_request::<lsp_types::request::ShowMessageRequest>( + lsp_types::ShowMessageRequestParams { + typ: lsp_types::MessageType::ERROR, + message, + actions: Some(vec![lsp_types::MessageActionItem { + title: "Open server logs".to_owned(), + properties: Default::default(), + }]), + }, + |this, resp| { + let lsp_server::Response { error: None, result: Some(result), .. } = resp + else { return }; + if let Ok(Some(_item)) = crate::from_json::< + <lsp_types::request::ShowMessageRequest as lsp_types::request::Request>::Result, + >( + lsp_types::request::ShowMessageRequest::METHOD, &result + ) { + this.send_notification::<lsp_ext::OpenServerLogs>(()); + } + }, + ), + false => self.send_notification::<lsp_types::notification::ShowMessage>( + lsp_types::ShowMessageParams { + typ: lsp_types::MessageType::ERROR, + message, + }, + ), } } - None => tracing::error!("{}", &message), - } + None => { + tracing::error!("{}", &message); - self.send_notification::<lsp_types::notification::ShowMessage>( - lsp_types::ShowMessageParams { typ: lsp_types::MessageType::ERROR, message }, - ) + self.send_notification::<lsp_types::notification::ShowMessage>( + lsp_types::ShowMessageParams { typ: lsp_types::MessageType::ERROR, message }, + ); + } + } } /// rust-analyzer is resilient -- if it fails, this doesn't usually affect @@ -77,7 +105,7 @@ impl GlobalState { let from_source_build = option_env!("POKE_RA_DEVS").is_some(); let profiling_enabled = std::env::var("RA_PROFILE").is_ok(); if from_source_build || profiling_enabled { - self.show_message(lsp_types::MessageType::ERROR, message) + self.show_and_log_error(message, None); } } @@ -133,6 +161,7 @@ impl GlobalState { } pub(crate) fn apply_document_changes( + encoding: PositionEncoding, file_contents: impl FnOnce() -> String, mut content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>, ) -> String { @@ -164,9 +193,9 @@ pub(crate) fn apply_document_changes( let mut line_index = LineIndex { // the index will be overwritten in the bottom loop's first iteration index: Arc::new(ide::LineIndex::new(&text)), - // We don't care about line endings or offset encoding here. + // We don't care about line endings here. endings: LineEndings::Unix, - encoding: PositionEncoding::Utf16, + encoding, }; // The changes we got must be applied sequentially, but can cross lines so we @@ -228,6 +257,7 @@ pub(crate) fn all_edits_are_disjoint( #[cfg(test)] mod tests { + use ide_db::line_index::WideEncoding; use lsp_types::{ CompletionItem, CompletionTextEdit, InsertReplaceEdit, Position, Range, TextDocumentContentChangeEvent, @@ -250,9 +280,11 @@ mod tests { }; } - let text = apply_document_changes(|| String::new(), vec![]); + let encoding = PositionEncoding::Wide(WideEncoding::Utf16); + let text = apply_document_changes(encoding, || String::new(), vec![]); assert_eq!(text, ""); let text = apply_document_changes( + encoding, || text, vec![TextDocumentContentChangeEvent { range: None, @@ -261,39 +293,49 @@ mod tests { }], ); assert_eq!(text, "the"); - let text = apply_document_changes(|| text, c![0, 3; 0, 3 => " quick"]); + let text = apply_document_changes(encoding, || text, c![0, 3; 0, 3 => " quick"]); assert_eq!(text, "the quick"); - let text = apply_document_changes(|| text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); + let text = + apply_document_changes(encoding, || text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); assert_eq!(text, "quick foxes"); - let text = apply_document_changes(|| text, c![0, 11; 0, 11 => "\ndream"]); + let text = apply_document_changes(encoding, || text, c![0, 11; 0, 11 => "\ndream"]); assert_eq!(text, "quick foxes\ndream"); - let text = apply_document_changes(|| text, c![1, 0; 1, 0 => "have "]); + let text = apply_document_changes(encoding, || text, c![1, 0; 1, 0 => "have "]); assert_eq!(text, "quick foxes\nhave dream"); let text = apply_document_changes( + encoding, || text, c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"], ); assert_eq!(text, "the quick foxes\nhave quiet dreams\n"); - let text = apply_document_changes(|| text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]); + let text = apply_document_changes( + encoding, + || text, + c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"], + ); assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n"); let text = apply_document_changes( + encoding, || text, c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"], ); assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n"); - let text = apply_document_changes(|| text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); + let text = + apply_document_changes(encoding, || text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); assert_eq!(text, "the quick \nthey have quiet dreams\n"); let text = String::from("❤️"); - let text = apply_document_changes(|| text, c![0, 0; 0, 0 => "a"]); + let text = apply_document_changes(encoding, || text, c![0, 0; 0, 0 => "a"]); assert_eq!(text, "a❤️"); let text = String::from("a\nb"); - let text = apply_document_changes(|| text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]); + let text = + apply_document_changes(encoding, || text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]); assert_eq!(text, "adcb"); let text = String::from("a\nb"); - let text = apply_document_changes(|| text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); + let text = + apply_document_changes(encoding, || text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); assert_eq!(text, "ațc\ncb"); } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs index 0bc940dfe..d1e38b33c 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs @@ -14,7 +14,7 @@ use ide_db::base_db::{SourceDatabaseExt, VfsPath}; use itertools::Itertools; use lsp_server::{Connection, Notification, Request}; use lsp_types::notification::Notification as _; -use vfs::{ChangeKind, FileId}; +use vfs::{AbsPathBuf, ChangeKind, FileId}; use crate::{ config::Config, @@ -287,8 +287,10 @@ impl GlobalState { || self.fetch_build_data_queue.op_requested()); if became_quiescent { - // Project has loaded properly, kick off initial flycheck - self.flycheck.iter().for_each(FlycheckHandle::restart); + if self.config.check_on_save() { + // Project has loaded properly, kick off initial flycheck + self.flycheck.iter().for_each(FlycheckHandle::restart); + } if self.config.prefill_caches() { self.prime_caches_queue.request_op("became quiescent".to_string()); } @@ -305,13 +307,18 @@ impl GlobalState { if self.config.code_lens_refresh() { self.send_request::<lsp_types::request::CodeLensRefresh>((), |_, _| ()); } - } - if !was_quiescent || state_changed || memdocs_added_or_removed { - if self.config.publish_diagnostics() { - self.update_diagnostics() + // Refresh inlay hints if the client supports it. + if self.config.inlay_hints_refresh() { + self.send_request::<lsp_types::request::InlayHintRefreshRequest>((), |_, _| ()); } } + + if (!was_quiescent || state_changed || memdocs_added_or_removed) + && self.config.publish_diagnostics() + { + self.update_diagnostics() + } } if let Some(diagnostic_changes) = self.diagnostics.take_changes() { @@ -604,8 +611,8 @@ impl GlobalState { Ok(()) }); - if let RequestDispatcher { req: Some(req), global_state: this } = &mut dispatcher { - if this.shutdown_requested { + match &mut dispatcher { + RequestDispatcher { req: Some(req), global_state: this } if this.shutdown_requested => { this.respond(lsp_server::Response::new_err( req.id.clone(), lsp_server::ErrorCode::InvalidRequest as i32, @@ -613,16 +620,7 @@ impl GlobalState { )); return; } - - // Avoid flashing a bunch of unresolved references during initial load. - if this.workspaces.is_empty() && !this.is_quiescent() { - this.respond(lsp_server::Response::new_err( - req.id.clone(), - lsp_server::ErrorCode::ContentModified as i32, - "waiting for cargo metadata or cargo check".to_owned(), - )); - return; - } + _ => (), } dispatcher @@ -833,6 +831,7 @@ impl GlobalState { let vfs = &mut this.vfs.write().0; let file_id = vfs.file_id(&path).unwrap(); let text = apply_document_changes( + this.config.position_encoding(), || std::str::from_utf8(vfs.file_contents(file_id)).unwrap().into(), params.content_changes, ); @@ -935,6 +934,30 @@ impl GlobalState { Ok(()) })? + .on::<lsp_types::notification::DidChangeWorkspaceFolders>(|this, params| { + let config = Arc::make_mut(&mut this.config); + + for workspace in params.event.removed { + let Ok(path) = workspace.uri.to_file_path() else { continue }; + let Ok(path) = AbsPathBuf::try_from(path) else { continue }; + let Some(position) = config.workspace_roots.iter().position(|it| it == &path) else { continue }; + config.workspace_roots.remove(position); + } + + let added = params + .event + .added + .into_iter() + .filter_map(|it| it.uri.to_file_path().ok()) + .filter_map(|it| AbsPathBuf::try_from(it).ok()); + config.workspace_roots.extend(added); + if !config.has_linked_projects() && config.detached_files().is_empty() { + config.rediscover_workspaces(); + this.fetch_workspaces_queue.request_op("client workspaces changed".to_string()) + } + + Ok(()) + })? .on::<lsp_types::notification::DidChangeWatchedFiles>(|this, params| { for change in params.changes { if let Ok(path) = from_proto::abs_path(&change.uri) { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs index 9bbce70ec..abce0d737 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -34,6 +34,8 @@ use crate::{ op_queue::Cause, }; +use ::tt::token_id as tt; + #[derive(Debug)] pub(crate) enum ProjectWorkspaceProgress { Begin, @@ -148,11 +150,11 @@ impl GlobalState { ) } LinkedProject::InlineJsonProject(it) => { - project_model::ProjectWorkspace::load_inline( + Ok(project_model::ProjectWorkspace::load_inline( it.clone(), cargo_config.target.as_deref(), &cargo_config.extra_env, - ) + )) } }) .collect::<Vec<_>>(); @@ -212,35 +214,11 @@ impl GlobalState { let workspaces = workspaces.iter().filter_map(|res| res.as_ref().ok().cloned()).collect::<Vec<_>>(); - fn eq_ignore_build_data<'a>( - left: &'a ProjectWorkspace, - right: &'a ProjectWorkspace, - ) -> bool { - let key = |p: &'a ProjectWorkspace| match p { - ProjectWorkspace::Cargo { - cargo, - sysroot, - rustc, - rustc_cfg, - cfg_overrides, - - build_scripts: _, - toolchain: _, - target_layout: _, - } => Some((cargo, sysroot, rustc, rustc_cfg, cfg_overrides)), - _ => None, - }; - match (key(left), key(right)) { - (Some(lk), Some(rk)) => lk == rk, - _ => left == right, - } - } - let same_workspaces = workspaces.len() == self.workspaces.len() && workspaces .iter() .zip(self.workspaces.iter()) - .all(|(l, r)| eq_ignore_build_data(l, r)); + .all(|(l, r)| l.eq_ignore_build_data(r)); if same_workspaces { let (workspaces, build_scripts) = self.fetch_build_data_queue.last_op_result(); @@ -270,7 +248,8 @@ impl GlobalState { // Here, we completely changed the workspace (Cargo.toml edit), so // we don't care about build-script results, they are stale. - self.workspaces = Arc::new(workspaces) + // FIXME: can we abort the build scripts here? + self.workspaces = Arc::new(workspaces); } if let FilesWatcher::Client = self.config.files().watcher { @@ -289,7 +268,10 @@ impl GlobalState { ] }) }) - .map(|glob_pattern| lsp_types::FileSystemWatcher { glob_pattern, kind: None }) + .map(|glob_pattern| lsp_types::FileSystemWatcher { + glob_pattern: lsp_types::GlobPattern::String(glob_pattern), + kind: None, + }) .collect(), }; let registration = lsp_types::Registration { @@ -362,7 +344,7 @@ impl GlobalState { let loader = &mut self.loader; let mem_docs = &self.mem_docs; let mut load = move |path: &AbsPath| { - let _p = profile::span("GlobalState::load"); + let _p = profile::span("switch_workspaces::load"); let vfs_path = vfs::VfsPath::from(path.to_path_buf()); if !mem_docs.contains(&vfs_path) { let contents = loader.handle.load_sync(path); @@ -584,10 +566,10 @@ pub(crate) fn load_proc_macro( path: &AbsPath, dummy_replace: &[Box<str>], ) -> ProcMacroLoadResult { + let server = server.map_err(ToOwned::to_owned)?; let res: Result<Vec<_>, String> = (|| { let dylib = MacroDylib::new(path.to_path_buf()) .map_err(|io| format!("Proc-macro dylib loading failed: {io}"))?; - let server = server.map_err(ToOwned::to_owned)?; let vec = server.load_dylib(dylib).map_err(|e| format!("{e}"))?; if vec.is_empty() { return Err("proc macro library returned no proc macros".to_string()); @@ -679,7 +661,7 @@ pub(crate) fn load_proc_macro( _: Option<&tt::Subtree>, _: &Env, ) -> Result<tt::Subtree, ProcMacroExpansionError> { - Ok(tt::Subtree::default()) + Ok(tt::Subtree::empty()) } } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs index e736b2ff9..92029dc1d 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs @@ -9,9 +9,9 @@ use ide::{ Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionItem, CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint, - InlayHintLabel, InlayKind, Markup, NavigationTarget, ReferenceCategory, RenameError, Runnable, - Severity, SignatureHelp, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, - TextSize, + InlayHintLabel, InlayHintLabelPart, InlayKind, Markup, NavigationTarget, ReferenceCategory, + RenameError, Runnable, Severity, SignatureHelp, SourceChange, StructureNodeKind, SymbolKind, + TextEdit, TextRange, TextSize, }; use itertools::Itertools; use serde_json::to_value; @@ -31,8 +31,8 @@ pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::P let line_col = line_index.index.line_col(offset); match line_index.encoding { PositionEncoding::Utf8 => lsp_types::Position::new(line_col.line, line_col.col), - PositionEncoding::Utf16 => { - let line_col = line_index.index.to_utf16(line_col); + PositionEncoding::Wide(enc) => { + let line_col = line_index.index.to_wide(enc, line_col); lsp_types::Position::new(line_col.line, line_col.col) } } @@ -212,11 +212,17 @@ pub(crate) fn completion_items( tdpp: lsp_types::TextDocumentPositionParams, items: Vec<CompletionItem>, ) -> Vec<lsp_types::CompletionItem> { - let max_relevance = items.iter().map(|it| it.relevance().score()).max().unwrap_or_default(); + let max_relevance = items.iter().map(|it| it.relevance.score()).max().unwrap_or_default(); let mut res = Vec::with_capacity(items.len()); for item in items { - completion_item(&mut res, config, line_index, &tdpp, max_relevance, item) + completion_item(&mut res, config, line_index, &tdpp, max_relevance, item); } + + if let Some(limit) = config.completion().limit { + res.sort_by(|item1, item2| item1.sort_text.cmp(&item2.sort_text)); + res.truncate(limit); + } + res } @@ -229,22 +235,26 @@ fn completion_item( item: CompletionItem, ) { let insert_replace_support = config.insert_replace_support().then_some(tdpp.position); + let ref_match = item.ref_match(); + let lookup = item.lookup().to_string(); + let mut additional_text_edits = Vec::new(); // LSP does not allow arbitrary edits in completion, so we have to do a // non-trivial mapping here. let text_edit = { let mut text_edit = None; - let source_range = item.source_range(); - for indel in item.text_edit().iter() { + let source_range = item.source_range; + for indel in item.text_edit { if indel.delete.contains_range(source_range) { + // Extract this indel as the main edit text_edit = Some(if indel.delete == source_range { self::completion_text_edit(line_index, insert_replace_support, indel.clone()) } else { assert!(source_range.end() == indel.delete.end()); let range1 = TextRange::new(indel.delete.start(), source_range.start()); let range2 = source_range; - let indel1 = Indel::replace(range1, String::new()); + let indel1 = Indel::delete(range1); let indel2 = Indel::replace(range2, indel.insert.clone()); additional_text_edits.push(self::text_edit(line_index, indel1)); self::completion_text_edit(line_index, insert_replace_support, indel2) @@ -258,23 +268,23 @@ fn completion_item( text_edit.unwrap() }; - let insert_text_format = item.is_snippet().then_some(lsp_types::InsertTextFormat::SNIPPET); - let tags = item.deprecated().then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]); - let command = if item.trigger_call_info() && config.client_commands().trigger_parameter_hints { + let insert_text_format = item.is_snippet.then_some(lsp_types::InsertTextFormat::SNIPPET); + let tags = item.deprecated.then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]); + let command = if item.trigger_call_info && config.client_commands().trigger_parameter_hints { Some(command::trigger_parameter_hints()) } else { None }; let mut lsp_item = lsp_types::CompletionItem { - label: item.label().to_string(), - detail: item.detail().map(|it| it.to_string()), - filter_text: Some(item.lookup().to_string()), - kind: Some(completion_item_kind(item.kind())), + label: item.label.to_string(), + detail: item.detail.map(|it| it.to_string()), + filter_text: Some(lookup), + kind: Some(completion_item_kind(item.kind)), text_edit: Some(text_edit), additional_text_edits: Some(additional_text_edits), - documentation: item.documentation().map(documentation), - deprecated: Some(item.deprecated()), + documentation: item.documentation.map(documentation), + deprecated: Some(item.deprecated), tags, command, insert_text_format, @@ -288,12 +298,13 @@ fn completion_item( }); } - set_score(&mut lsp_item, max_relevance, item.relevance()); + set_score(&mut lsp_item, max_relevance, item.relevance); if config.completion().enable_imports_on_the_fly { - if let imports @ [_, ..] = item.imports_to_add() { - let imports: Vec<_> = imports - .iter() + if !item.import_to_add.is_empty() { + let imports: Vec<_> = item + .import_to_add + .into_iter() .filter_map(|import_edit| { let import_path = &import_edit.import_path; let import_name = import_path.segments().last()?; @@ -310,18 +321,13 @@ fn completion_item( } } - if let Some((mutability, offset, relevance)) = item.ref_match() { - let mut lsp_item_with_ref = lsp_item.clone(); + if let Some((label, indel, relevance)) = ref_match { + let mut lsp_item_with_ref = lsp_types::CompletionItem { label, ..lsp_item.clone() }; + lsp_item_with_ref + .additional_text_edits + .get_or_insert_with(Default::default) + .push(self::text_edit(line_index, indel)); set_score(&mut lsp_item_with_ref, max_relevance, relevance); - lsp_item_with_ref.label = - format!("&{}{}", mutability.as_keyword_for_ref(), lsp_item_with_ref.label); - lsp_item_with_ref.additional_text_edits.get_or_insert_with(Default::default).push( - self::text_edit( - line_index, - Indel::insert(offset, format!("&{}", mutability.as_keyword_for_ref())), - ), - ); - acc.push(lsp_item_with_ref); }; @@ -431,137 +437,140 @@ pub(crate) fn inlay_hint( mut inlay_hint: InlayHint, ) -> Cancellable<lsp_types::InlayHint> { match inlay_hint.kind { - InlayKind::ParameterHint if render_colons => inlay_hint.label.append_str(":"), - InlayKind::TypeHint if render_colons => inlay_hint.label.prepend_str(": "), - InlayKind::ClosureReturnTypeHint => inlay_hint.label.prepend_str(" -> "), - InlayKind::DiscriminantHint => inlay_hint.label.prepend_str(" = "), + InlayKind::Parameter if render_colons => inlay_hint.label.append_str(":"), + InlayKind::Type if render_colons => inlay_hint.label.prepend_str(": "), + InlayKind::ClosureReturnType => inlay_hint.label.prepend_str(" -> "), + InlayKind::Discriminant => inlay_hint.label.prepend_str(" = "), _ => {} } + let (label, tooltip) = inlay_hint_label(snap, inlay_hint.label)?; + Ok(lsp_types::InlayHint { position: match inlay_hint.kind { // before annotated thing InlayKind::OpeningParenthesis - | InlayKind::ParameterHint - | InlayKind::AdjustmentHint - | InlayKind::BindingModeHint => position(line_index, inlay_hint.range.start()), + | InlayKind::Parameter + | InlayKind::Adjustment + | InlayKind::BindingMode => position(line_index, inlay_hint.range.start()), // after annotated thing - InlayKind::ClosureReturnTypeHint - | InlayKind::TypeHint - | InlayKind::DiscriminantHint - | InlayKind::ChainingHint - | InlayKind::GenericParamListHint + InlayKind::ClosureReturnType + | InlayKind::Type + | InlayKind::Discriminant + | InlayKind::Chaining + | InlayKind::GenericParamList | InlayKind::ClosingParenthesis - | InlayKind::AdjustmentHintPostfix - | InlayKind::LifetimeHint - | InlayKind::ClosingBraceHint => position(line_index, inlay_hint.range.end()), + | InlayKind::AdjustmentPostfix + | InlayKind::Lifetime + | InlayKind::ClosingBrace => position(line_index, inlay_hint.range.end()), }, padding_left: Some(match inlay_hint.kind { - InlayKind::TypeHint => !render_colons, - InlayKind::ChainingHint | InlayKind::ClosingBraceHint => true, + InlayKind::Type => !render_colons, + InlayKind::Chaining | InlayKind::ClosingBrace => true, InlayKind::ClosingParenthesis - | InlayKind::DiscriminantHint + | InlayKind::Discriminant | InlayKind::OpeningParenthesis - | InlayKind::BindingModeHint - | InlayKind::ClosureReturnTypeHint - | InlayKind::GenericParamListHint - | InlayKind::AdjustmentHint - | InlayKind::AdjustmentHintPostfix - | InlayKind::LifetimeHint - | InlayKind::ParameterHint => false, + | InlayKind::BindingMode + | InlayKind::ClosureReturnType + | InlayKind::GenericParamList + | InlayKind::Adjustment + | InlayKind::AdjustmentPostfix + | InlayKind::Lifetime + | InlayKind::Parameter => false, }), padding_right: Some(match inlay_hint.kind { InlayKind::ClosingParenthesis | InlayKind::OpeningParenthesis - | InlayKind::ChainingHint - | InlayKind::ClosureReturnTypeHint - | InlayKind::GenericParamListHint - | InlayKind::AdjustmentHint - | InlayKind::AdjustmentHintPostfix - | InlayKind::TypeHint - | InlayKind::DiscriminantHint - | InlayKind::ClosingBraceHint => false, - InlayKind::BindingModeHint => inlay_hint.label.as_simple_str() != Some("&"), - InlayKind::ParameterHint | InlayKind::LifetimeHint => true, + | InlayKind::Chaining + | InlayKind::ClosureReturnType + | InlayKind::GenericParamList + | InlayKind::Adjustment + | InlayKind::AdjustmentPostfix + | InlayKind::Type + | InlayKind::Discriminant + | InlayKind::ClosingBrace => false, + InlayKind::BindingMode => { + matches!(&label, lsp_types::InlayHintLabel::String(s) if s != "&") + } + InlayKind::Parameter | InlayKind::Lifetime => true, }), kind: match inlay_hint.kind { - InlayKind::ParameterHint => Some(lsp_types::InlayHintKind::PARAMETER), - InlayKind::ClosureReturnTypeHint | InlayKind::TypeHint | InlayKind::ChainingHint => { + InlayKind::Parameter => Some(lsp_types::InlayHintKind::PARAMETER), + InlayKind::ClosureReturnType | InlayKind::Type | InlayKind::Chaining => { Some(lsp_types::InlayHintKind::TYPE) } InlayKind::ClosingParenthesis - | InlayKind::DiscriminantHint + | InlayKind::Discriminant | InlayKind::OpeningParenthesis - | InlayKind::BindingModeHint - | InlayKind::GenericParamListHint - | InlayKind::LifetimeHint - | InlayKind::AdjustmentHint - | InlayKind::AdjustmentHintPostfix - | InlayKind::ClosingBraceHint => None, + | InlayKind::BindingMode + | InlayKind::GenericParamList + | InlayKind::Lifetime + | InlayKind::Adjustment + | InlayKind::AdjustmentPostfix + | InlayKind::ClosingBrace => None, }, text_edits: None, - data: (|| match inlay_hint.tooltip { - Some(ide::InlayTooltip::HoverOffset(file_id, offset)) => { - let uri = url(snap, file_id); - let line_index = snap.file_line_index(file_id).ok()?; - - let text_document = lsp_types::VersionedTextDocumentIdentifier { - version: snap.url_file_version(&uri)?, - uri, - }; - to_value(lsp_ext::InlayHintResolveData { - text_document, - position: lsp_ext::PositionOrRange::Position(position(&line_index, offset)), - }) - .ok() - } - Some(ide::InlayTooltip::HoverRanged(file_id, text_range)) => { - let uri = url(snap, file_id); - let text_document = lsp_types::VersionedTextDocumentIdentifier { - version: snap.url_file_version(&uri)?, - uri, - }; - let line_index = snap.file_line_index(file_id).ok()?; - to_value(lsp_ext::InlayHintResolveData { - text_document, - position: lsp_ext::PositionOrRange::Range(range(&line_index, text_range)), - }) - .ok() - } - _ => None, - })(), - tooltip: Some(match inlay_hint.tooltip { - Some(ide::InlayTooltip::String(s)) => lsp_types::InlayHintTooltip::String(s), - _ => lsp_types::InlayHintTooltip::String(inlay_hint.label.to_string()), - }), - label: inlay_hint_label(snap, inlay_hint.label)?, + data: None, + tooltip, + label, }) } fn inlay_hint_label( snap: &GlobalStateSnapshot, - label: InlayHintLabel, -) -> Cancellable<lsp_types::InlayHintLabel> { - Ok(match label.as_simple_str() { - Some(s) => lsp_types::InlayHintLabel::String(s.into()), - None => lsp_types::InlayHintLabel::LabelParts( - label + mut label: InlayHintLabel, +) -> Cancellable<(lsp_types::InlayHintLabel, Option<lsp_types::InlayHintTooltip>)> { + let res = match &*label.parts { + [InlayHintLabelPart { linked_location: None, .. }] => { + let InlayHintLabelPart { text, tooltip, .. } = label.parts.pop().unwrap(); + ( + lsp_types::InlayHintLabel::String(text), + match tooltip { + Some(ide::InlayTooltip::String(s)) => { + Some(lsp_types::InlayHintTooltip::String(s)) + } + Some(ide::InlayTooltip::Markdown(s)) => { + Some(lsp_types::InlayHintTooltip::MarkupContent(lsp_types::MarkupContent { + kind: lsp_types::MarkupKind::Markdown, + value: s, + })) + } + None => None, + }, + ) + } + _ => { + let parts = label .parts .into_iter() .map(|part| { - Ok(lsp_types::InlayHintLabelPart { - value: part.text, - tooltip: None, - location: part - .linked_location - .map(|range| location(snap, range)) - .transpose()?, - command: None, - }) + part.linked_location.map(|range| location(snap, range)).transpose().map( + |location| lsp_types::InlayHintLabelPart { + value: part.text, + tooltip: match part.tooltip { + Some(ide::InlayTooltip::String(s)) => { + Some(lsp_types::InlayHintLabelPartTooltip::String(s)) + } + Some(ide::InlayTooltip::Markdown(s)) => { + Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent( + lsp_types::MarkupContent { + kind: lsp_types::MarkupKind::Markdown, + value: s, + }, + )) + } + None => None, + }, + location, + command: None, + }, + ) }) - .collect::<Cancellable<Vec<_>>>()?, - ), - }) + .collect::<Cancellable<_>>()?; + (lsp_types::InlayHintLabel::LabelParts(parts), None) + } + }; + Ok(res) } static TOKEN_RESULT_COUNTER: AtomicU32 = AtomicU32::new(1); @@ -757,6 +766,7 @@ pub(crate) fn folding_range( end_line, end_character: None, kind, + collapsed_text: None, } } else { lsp_types::FoldingRange { @@ -765,6 +775,7 @@ pub(crate) fn folding_range( end_line: range.end.line, end_character: Some(range.end.character), kind, + collapsed_text: None, } } } @@ -1351,7 +1362,7 @@ pub(crate) mod command { pub(crate) fn trigger_parameter_hints() -> lsp_types::Command { lsp_types::Command { title: "triggerParameterHints".into(), - command: "editor.action.triggerParameterHints".into(), + command: "rust-analyzer.triggerParameterHints".into(), arguments: None, } } @@ -1420,7 +1431,7 @@ fn main() { let line_index = LineIndex { index: Arc::new(ide::LineIndex::new(text)), endings: LineEndings::Unix, - encoding: PositionEncoding::Utf16, + encoding: PositionEncoding::Utf8, }; let converted: Vec<lsp_types::FoldingRange> = folds.into_iter().map(|it| folding_range(text, &line_index, true, it)).collect(); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs index 5e3e19d44..587d64096 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs @@ -22,7 +22,7 @@ use lsp_types::{ notification::DidOpenTextDocument, request::{ CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest, - WillRenameFiles, WorkspaceSymbol, + WillRenameFiles, WorkspaceSymbolRequest, }, CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams, DocumentFormattingParams, FileRename, FormattingOptions, GotoDefinitionParams, HoverParams, @@ -1095,5 +1095,5 @@ pub fn bar() {} .server() .wait_until_workspace_is_loaded(); - server.request::<WorkspaceSymbol>(Default::default(), json!([])); + server.request::<WorkspaceSymbolRequest>(Default::default(), json!([])); } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs index 269212ebb..037fc89ac 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs @@ -107,6 +107,7 @@ impl<'a> Project<'a> { did_change_watched_files: Some( lsp_types::DidChangeWatchedFilesClientCapabilities { dynamic_registration: Some(true), + relative_pattern_support: None, }, ), ..Default::default() @@ -137,6 +138,7 @@ impl<'a> Project<'a> { })), ..Default::default() }, + Vec::new(), ); config.discovered_projects = Some(discovered_projects); config.update(self.config).expect("invalid config"); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/tidy.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/tidy.rs index 35b5af731..8e3097fce 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/tidy.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/tidy.rs @@ -82,7 +82,6 @@ fn files_are_tidy() { check_dbg(&path, &text); check_test_attrs(&path, &text); check_trailing_ws(&path, &text); - deny_clippy(&path, &text); tidy_docs.visit(&path, &text); tidy_marks.visit(&path, &text); } @@ -144,32 +143,6 @@ fn check_cargo_toml(path: &Path, text: String) { } } -fn deny_clippy(path: &Path, text: &str) { - let ignore = &[ - // The documentation in string literals may contain anything for its own purposes - "ide-db/src/generated/lints.rs", - // The tests test clippy lint hovers - "ide/src/hover/tests.rs", - // The tests test clippy lint completions - "ide-completion/src/tests/attribute.rs", - ]; - if ignore.iter().any(|p| path.ends_with(p)) { - return; - } - - if text.contains("\u{61}llow(clippy") { - panic!( - "\n\nallowing lints is forbidden: {}. -rust-analyzer intentionally doesn't check clippy on CI. -You can allow lint globally via `xtask clippy`. -See https://github.com/rust-lang/rust-clippy/issues/5537 for discussion. - -", - path.display() - ) - } -} - #[cfg(not(feature = "in-rust-tree"))] #[test] fn check_licenses() { diff --git a/src/tools/rust-analyzer/crates/sourcegen/Cargo.toml b/src/tools/rust-analyzer/crates/sourcegen/Cargo.toml index 593dc4e55..fb2b9ebef 100644 --- a/src/tools/rust-analyzer/crates/sourcegen/Cargo.toml +++ b/src/tools/rust-analyzer/crates/sourcegen/Cargo.toml @@ -2,9 +2,11 @@ name = "sourcegen" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false diff --git a/src/tools/rust-analyzer/crates/stdx/Cargo.toml b/src/tools/rust-analyzer/crates/stdx/Cargo.toml index f7b7d0964..c881f2fd3 100644 --- a/src/tools/rust-analyzer/crates/stdx/Cargo.toml +++ b/src/tools/rust-analyzer/crates/stdx/Cargo.toml @@ -2,9 +2,11 @@ name = "stdx" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false diff --git a/src/tools/rust-analyzer/crates/stdx/src/lib.rs b/src/tools/rust-analyzer/crates/stdx/src/lib.rs index 51e109798..5639aaf57 100644 --- a/src/tools/rust-analyzer/crates/stdx/src/lib.rs +++ b/src/tools/rust-analyzer/crates/stdx/src/lib.rs @@ -2,15 +2,16 @@ #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)] +use std::io as sio; use std::process::Command; use std::{cmp::Ordering, ops, time::Instant}; -use std::{io as sio, iter}; mod macros; pub mod hash; pub mod process; pub mod panic_context; pub mod non_empty_vec; +pub mod rand; pub use always_assert::{always, never}; @@ -39,15 +40,19 @@ Uncomment `default = [ "backtrace" ]` in `crates/stdx/Cargo.toml`. } pub fn to_lower_snake_case(s: &str) -> String { - to_snake_case(s, char::to_ascii_lowercase) + to_snake_case(s, char::to_lowercase) } pub fn to_upper_snake_case(s: &str) -> String { - to_snake_case(s, char::to_ascii_uppercase) + to_snake_case(s, char::to_uppercase) } // Code partially taken from rust/compiler/rustc_lint/src/nonstandard_style.rs // commit: 9626f2b -fn to_snake_case<F: Fn(&char) -> char>(mut s: &str, change_case: F) -> String { +fn to_snake_case<F, I>(mut s: &str, change_case: F) -> String +where + F: Fn(char) -> I, + I: Iterator<Item = char>, +{ let mut words = vec![]; // Preserve leading underscores @@ -75,7 +80,7 @@ fn to_snake_case<F: Fn(&char) -> char>(mut s: &str, change_case: F) -> String { } last_upper = ch.is_uppercase(); - buf.extend(iter::once(change_case(&ch))); + buf.extend(change_case(ch)); } words.push(buf); diff --git a/src/tools/rust-analyzer/crates/stdx/src/macros.rs b/src/tools/rust-analyzer/crates/stdx/src/macros.rs index d91fc690c..1a9982fa8 100644 --- a/src/tools/rust-analyzer/crates/stdx/src/macros.rs +++ b/src/tools/rust-analyzer/crates/stdx/src/macros.rs @@ -43,5 +43,14 @@ macro_rules! impl_from { } )*)? )* + }; + ($($variant:ident$(<$V:ident>)?),* for $enum:ident) => { + $( + impl$(<$V>)? From<$variant$(<$V>)?> for $enum$(<$V>)? { + fn from(it: $variant$(<$V>)?) -> $enum$(<$V>)? { + $enum::$variant(it) + } + } + )* } } diff --git a/src/tools/rust-analyzer/crates/stdx/src/rand.rs b/src/tools/rust-analyzer/crates/stdx/src/rand.rs new file mode 100644 index 000000000..64aa57eae --- /dev/null +++ b/src/tools/rust-analyzer/crates/stdx/src/rand.rs @@ -0,0 +1,21 @@ +//! We don't use `rand`, as that's too many things for us. +//! +//! We currently use oorandom instead, but it's missing these two utilities. +//! Perhaps we should switch to `fastrand`, or our own small PRNG, it's not like +//! we need anything more complicated than xor-shift. + +pub fn shuffle<T>(slice: &mut [T], mut rand_index: impl FnMut(usize) -> usize) { + let mut remaining = slice.len() - 1; + while remaining > 0 { + let index = rand_index(remaining); + slice.swap(remaining, index); + remaining -= 1; + } +} + +pub fn seed() -> u64 { + use std::collections::hash_map::RandomState; + use std::hash::{BuildHasher, Hasher}; + + RandomState::new().build_hasher().finish() +} diff --git a/src/tools/rust-analyzer/crates/syntax/Cargo.toml b/src/tools/rust-analyzer/crates/syntax/Cargo.toml index 00743cca5..8fc493a23 100644 --- a/src/tools/rust-analyzer/crates/syntax/Cargo.toml +++ b/src/tools/rust-analyzer/crates/syntax/Cargo.toml @@ -2,10 +2,12 @@ name = "syntax" version = "0.0.0" description = "Comment and whitespace preserving parser for the Rust language" -license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/rust-analyzer" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -14,26 +16,26 @@ doctest = false cov-mark = "2.0.0-pre.1" itertools = "0.10.5" rowan = "0.15.10" -rustc_lexer = { version = "725.0.0", package = "rustc-ap-rustc_lexer" } +rustc_lexer = { version = "727.0.0", package = "rustc-ap-rustc_lexer" } rustc-hash = "1.1.0" -once_cell = "1.15.0" +once_cell = "1.17.0" indexmap = "1.9.1" smol_str = "0.1.23" -stdx = { path = "../stdx", version = "0.0.0" } -text-edit = { path = "../text-edit", version = "0.0.0" } -parser = { path = "../parser", version = "0.0.0" } -profile = { path = "../profile", version = "0.0.0" } +parser.workspace = true +profile.workspace = true +stdx.workspace = true +text-edit.workspace = true [dev-dependencies] -rayon = "1.5.3" +rayon = "1.6.1" expect-test = "1.4.0" proc-macro2 = "1.0.47" quote = "1.0.20" ungrammar = "1.16.1" -test-utils = { path = "../test-utils" } -sourcegen = { path = "../sourcegen" } +test-utils.workspace = true +sourcegen.workspace = true [features] in-rust-tree = [] diff --git a/src/tools/rust-analyzer/crates/syntax/fuzz/Cargo.toml b/src/tools/rust-analyzer/crates/syntax/fuzz/Cargo.toml index f295c4006..6070222f1 100644 --- a/src/tools/rust-analyzer/crates/syntax/fuzz/Cargo.toml +++ b/src/tools/rust-analyzer/crates/syntax/fuzz/Cargo.toml @@ -1,18 +1,17 @@ - [package] name = "syntax-fuzz" version = "0.0.1" publish = false edition = "2021" -rust-version = "1.65" +rust-version = "1.66.1" [package.metadata] cargo-fuzz = true [dependencies] syntax = { path = "..", version = "0.0.0" } -text_edit = { path = "../../text_edit", version = "0.0.0" } -libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git" } +text-edit = { path = "../../text-edit", version = "0.0.0" } +libfuzzer-sys = "0.4.5" # Prevent this from interfering with workspaces [workspace] diff --git a/src/tools/rust-analyzer/crates/syntax/rust.ungram b/src/tools/rust-analyzer/crates/syntax/rust.ungram index 2c67586a3..36ad5fddf 100644 --- a/src/tools/rust-analyzer/crates/syntax/rust.ungram +++ b/src/tools/rust-analyzer/crates/syntax/rust.ungram @@ -452,7 +452,7 @@ FieldExpr = Attr* Expr '.' NameRef ClosureExpr = - Attr* ('for' GenericParamList)? 'static'? 'async'? 'move'? ParamList RetType? + Attr* ('for' GenericParamList)? 'const'? 'static'? 'async'? 'move'? ParamList RetType? body:Expr IfExpr = diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast.rs b/src/tools/rust-analyzer/crates/syntax/src/ast.rs index 10c045758..385a4e0a3 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast.rs @@ -13,6 +13,8 @@ pub mod prec; use std::marker::PhantomData; +use itertools::Either; + use crate::{ syntax_node::{SyntaxNode, SyntaxNodeChildren, SyntaxToken}, SyntaxKind, @@ -98,6 +100,34 @@ impl<N: AstNode> Iterator for AstChildren<N> { } } +impl<L, R> AstNode for Either<L, R> +where + L: AstNode, + R: AstNode, +{ + fn can_cast(kind: SyntaxKind) -> bool + where + Self: Sized, + { + L::can_cast(kind) || R::can_cast(kind) + } + + fn cast(syntax: SyntaxNode) -> Option<Self> + where + Self: Sized, + { + if L::can_cast(syntax.kind()) { + L::cast(syntax).map(Either::Left) + } else { + R::cast(syntax).map(Either::Right) + } + } + + fn syntax(&self) -> &SyntaxNode { + self.as_ref().either(L::syntax, R::syntax) + } +} + mod support { use super::{AstChildren, AstNode, SyntaxKind, SyntaxNode, SyntaxToken}; diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs index a214a5e44..642a3bfc3 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs @@ -842,6 +842,7 @@ impl ast::HasAttrs for ClosureExpr {} impl ClosureExpr { pub fn for_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![for]) } pub fn generic_param_list(&self) -> Option<GenericParamList> { support::child(&self.syntax) } + pub fn const_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![const]) } pub fn static_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![static]) } pub fn async_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![async]) } pub fn move_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![move]) } diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs index d5b329698..5aebe4cd9 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs @@ -12,7 +12,7 @@ use itertools::Itertools; use stdx::{format_to, never}; -use crate::{ast, AstNode, SourceFile, SyntaxKind, SyntaxToken}; +use crate::{ast, utils::is_raw_identifier, AstNode, SourceFile, SyntaxKind, SyntaxToken}; /// While the parent module defines basic atomic "constructors", the `ext` /// module defines shortcuts for common things. @@ -111,8 +111,7 @@ pub fn name_ref(name_ref: &str) -> ast::NameRef { ast_from_text(&format!("fn f() {{ {raw_escape}{name_ref}; }}")) } fn raw_ident_esc(ident: &str) -> &'static str { - let is_keyword = parser::SyntaxKind::from_keyword(ident).is_some(); - if is_keyword && !matches!(ident, "self" | "crate" | "super" | "Self") { + if is_raw_identifier(ident) { "r#" } else { "" @@ -520,6 +519,15 @@ pub fn literal_pat(lit: &str) -> ast::LiteralPat { } } +pub fn slice_pat(pats: impl IntoIterator<Item = ast::Pat>) -> ast::SlicePat { + let pats_str = pats.into_iter().join(", "); + return from_text(&format!("[{pats_str}]")); + + fn from_text(text: &str) -> ast::SlicePat { + ast_from_text(&format!("fn f() {{ match () {{{text} => ()}} }}")) + } +} + /// Creates a tuple of patterns from an iterator of patterns. /// /// Invariant: `pats` must be length > 0 @@ -814,6 +822,7 @@ pub fn fn_( visibility: Option<ast::Visibility>, fn_name: ast::Name, type_params: Option<ast::GenericParamList>, + where_clause: Option<ast::WhereClause>, params: ast::ParamList, body: ast::BlockExpr, ret_type: Option<ast::RetType>, @@ -823,6 +832,10 @@ pub fn fn_( Some(type_params) => format!("{type_params}"), None => "".into(), }; + let where_clause = match where_clause { + Some(it) => format!("{it} "), + None => "".into(), + }; let ret_type = match ret_type { Some(ret_type) => format!("{ret_type} "), None => "".into(), @@ -835,7 +848,7 @@ pub fn fn_( let async_literal = if is_async { "async " } else { "" }; ast_from_text(&format!( - "{visibility}{async_literal}fn {fn_name}{type_params}{params} {ret_type}{body}", + "{visibility}{async_literal}fn {fn_name}{type_params}{params} {ret_type}{where_clause}{body}", )) } diff --git a/src/tools/rust-analyzer/crates/syntax/src/lib.rs b/src/tools/rust-analyzer/crates/syntax/src/lib.rs index 84c66b27e..6f57cbad6 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/lib.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/lib.rs @@ -186,7 +186,7 @@ impl SourceFile { /// ``` #[macro_export] macro_rules! match_ast { - (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) }; + (match $node:ident { $($tt:tt)* }) => { $crate::match_ast!(match ($node) { $($tt)* }) }; (match ($node:expr) { $( $( $path:ident )::+ ($it:pat) => $res:expr, )* diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_node.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_node.rs index a08c01597..2e9e0bc22 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_node.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_node.rs @@ -48,6 +48,7 @@ impl SyntaxTreeBuilder { pub fn finish(self) -> Parse<SyntaxNode> { let (green, errors) = self.finish_raw(); // Disable block validation, see https://github.com/rust-lang/rust-analyzer/pull/10357 + #[allow(clippy::overly_complex_bool_expr)] if cfg!(debug_assertions) && false { let node = SyntaxNode::new_root(green.clone()); crate::validation::validate_block_structure(&node); diff --git a/src/tools/rust-analyzer/crates/syntax/src/utils.rs b/src/tools/rust-analyzer/crates/syntax/src/utils.rs index f4c02518b..25f34ea9d 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/utils.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/utils.rs @@ -2,7 +2,7 @@ use itertools::Itertools; -use crate::{ast, match_ast, AstNode}; +use crate::{ast, match_ast, AstNode, SyntaxKind}; pub fn path_to_string_stripping_turbo_fish(path: &ast::Path) -> String { path.syntax() @@ -23,6 +23,11 @@ pub fn path_to_string_stripping_turbo_fish(path: &ast::Path) -> String { .join("::") } +pub fn is_raw_identifier(name: &str) -> bool { + let is_keyword = SyntaxKind::from_keyword(name).is_some(); + is_keyword && !matches!(name, "self" | "crate" | "super" | "Self") +} + #[cfg(test)] mod tests { use super::path_to_string_stripping_turbo_fish; diff --git a/src/tools/rust-analyzer/crates/test-utils/Cargo.toml b/src/tools/rust-analyzer/crates/test-utils/Cargo.toml index 1047373b1..92b1ef23e 100644 --- a/src/tools/rust-analyzer/crates/test-utils/Cargo.toml +++ b/src/tools/rust-analyzer/crates/test-utils/Cargo.toml @@ -2,9 +2,11 @@ name = "test-utils" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -15,5 +17,5 @@ dissimilar = "1.0.4" text-size = "1.1.0" rustc-hash = "1.1.0" -stdx = { path = "../stdx", version = "0.0.0" } -profile = { path = "../profile", version = "0.0.0" } +stdx.workspace = true +profile.workspace = true diff --git a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs index 3ca63fcab..3b033e1aa 100644 --- a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs +++ b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs @@ -28,6 +28,7 @@ //! generator: pin //! hash: //! index: sized +//! infallible: //! iterator: option //! iterators: iterator, fn //! non_zero: @@ -40,7 +41,7 @@ //! sized: //! slice: //! sync: sized -//! try: +//! try: infallible //! unsize: sized pub mod marker { @@ -105,6 +106,11 @@ pub mod marker { impl<T: ?Sized> Copy for &T {} } // endregion:copy + + // region:fn + #[lang = "tuple_trait"] + pub trait Tuple {} + // endregion:fn } // region:default @@ -172,6 +178,9 @@ pub mod convert { fn as_ref(&self) -> &T; } // endregion:as_ref + // region:infallible + pub enum Infallibe {} + // endregion:infallible } pub mod ops { @@ -269,6 +278,24 @@ pub mod ops { } } + impl<T, I, const N: usize> Index<I> for [T; N] + where + I: SliceIndex<[T]>, + { + type Output = I::Output; + fn index(&self, index: I) -> &I::Output { + loop {} + } + } + impl<T, I, const N: usize> IndexMut<I> for [T; N] + where + I: SliceIndex<[T]>, + { + fn index_mut(&mut self, index: I) -> &mut I::Output { + loop {} + } + } + pub unsafe trait SliceIndex<T: ?Sized> { type Output: ?Sized; } @@ -325,19 +352,26 @@ pub mod ops { // region:fn mod function { + use crate::marker::Tuple; + #[lang = "fn"] #[fundamental] - pub trait Fn<Args>: FnMut<Args> {} + pub trait Fn<Args: Tuple>: FnMut<Args> { + extern "rust-call" fn call(&self, args: Args) -> Self::Output; + } #[lang = "fn_mut"] #[fundamental] - pub trait FnMut<Args>: FnOnce<Args> {} + pub trait FnMut<Args: Tuple>: FnOnce<Args> { + extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output; + } #[lang = "fn_once"] #[fundamental] - pub trait FnOnce<Args> { + pub trait FnOnce<Args: Tuple> { #[lang = "fn_once_output"] type Output; + extern "rust-call" fn call_once(self, args: Args) -> Self::Output; } } pub use self::function::{Fn, FnMut, FnOnce}; @@ -352,7 +386,7 @@ pub mod ops { #[lang = "from_residual"] fn from_residual(residual: R) -> Self; } - #[lang = "try"] + #[lang = "Try"] pub trait Try: FromResidual<Self::Residual> { type Output; type Residual; diff --git a/src/tools/rust-analyzer/crates/text-edit/Cargo.toml b/src/tools/rust-analyzer/crates/text-edit/Cargo.toml index 8df7e1af6..337cd2347 100644 --- a/src/tools/rust-analyzer/crates/text-edit/Cargo.toml +++ b/src/tools/rust-analyzer/crates/text-edit/Cargo.toml @@ -2,9 +2,11 @@ name = "text-edit" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false diff --git a/src/tools/rust-analyzer/crates/toolchain/Cargo.toml b/src/tools/rust-analyzer/crates/toolchain/Cargo.toml index a6a3ae742..a283f9a88 100644 --- a/src/tools/rust-analyzer/crates/toolchain/Cargo.toml +++ b/src/tools/rust-analyzer/crates/toolchain/Cargo.toml @@ -2,9 +2,11 @@ name = "toolchain" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false diff --git a/src/tools/rust-analyzer/crates/tt/Cargo.toml b/src/tools/rust-analyzer/crates/tt/Cargo.toml index 4f2103f3a..b84693831 100644 --- a/src/tools/rust-analyzer/crates/tt/Cargo.toml +++ b/src/tools/rust-analyzer/crates/tt/Cargo.toml @@ -2,9 +2,11 @@ name = "tt" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -12,4 +14,4 @@ doctest = false [dependencies] smol_str = "0.1.23" -stdx = { path = "../stdx", version = "0.0.0" } +stdx.workspace = true diff --git a/src/tools/rust-analyzer/crates/tt/src/buffer.rs b/src/tools/rust-analyzer/crates/tt/src/buffer.rs index d27a7aa0d..0615a3763 100644 --- a/src/tools/rust-analyzer/crates/tt/src/buffer.rs +++ b/src/tools/rust-analyzer/crates/tt/src/buffer.rs @@ -7,33 +7,43 @@ use crate::{Leaf, Subtree, TokenTree}; struct EntryId(usize); #[derive(Copy, Clone, Debug, Eq, PartialEq)] -struct EntryPtr(EntryId, usize); +struct EntryPtr( + /// The index of the buffer containing the entry. + EntryId, + /// The index of the entry within the buffer. + usize, +); /// Internal type which is used instead of `TokenTree` to represent a token tree /// within a `TokenBuffer`. #[derive(Debug)] -enum Entry<'t> { +enum Entry<'t, Span> { // Mimicking types from proc-macro. - Subtree(Option<&'t TokenTree>, &'t Subtree, EntryId), - Leaf(&'t TokenTree), - // End entries contain a pointer to the entry from the containing - // token tree, or None if this is the outermost level. + Subtree(Option<&'t TokenTree<Span>>, &'t Subtree<Span>, EntryId), + Leaf(&'t TokenTree<Span>), + /// End entries contain a pointer to the entry from the containing + /// token tree, or [`None`] if this is the outermost level. End(Option<EntryPtr>), } /// A token tree buffer /// The safe version of `syn` [`TokenBuffer`](https://github.com/dtolnay/syn/blob/6533607f91686545cb034d2838beea338d9d0742/src/buffer.rs#L41) #[derive(Debug)] -pub struct TokenBuffer<'t> { - buffers: Vec<Box<[Entry<'t>]>>, +pub struct TokenBuffer<'t, Span> { + buffers: Vec<Box<[Entry<'t, Span>]>>, } -trait TokenList<'a> { - fn entries(&self) -> (Vec<(usize, (&'a Subtree, Option<&'a TokenTree>))>, Vec<Entry<'a>>); +trait TokenList<'a, Span> { + fn entries( + &self, + ) -> (Vec<(usize, (&'a Subtree<Span>, Option<&'a TokenTree<Span>>))>, Vec<Entry<'a, Span>>); } -impl<'a> TokenList<'a> for &'a [TokenTree] { - fn entries(&self) -> (Vec<(usize, (&'a Subtree, Option<&'a TokenTree>))>, Vec<Entry<'a>>) { +impl<'a, Span> TokenList<'a, Span> for &'a [TokenTree<Span>] { + fn entries( + &self, + ) -> (Vec<(usize, (&'a Subtree<Span>, Option<&'a TokenTree<Span>>))>, Vec<Entry<'a, Span>>) + { // Must contain everything in tokens and then the Entry::End let start_capacity = self.len() + 1; let mut entries = Vec::with_capacity(start_capacity); @@ -53,8 +63,11 @@ impl<'a> TokenList<'a> for &'a [TokenTree] { } } -impl<'a> TokenList<'a> for &'a Subtree { - fn entries(&self) -> (Vec<(usize, (&'a Subtree, Option<&'a TokenTree>))>, Vec<Entry<'a>>) { +impl<'a, Span> TokenList<'a, Span> for &'a Subtree<Span> { + fn entries( + &self, + ) -> (Vec<(usize, (&'a Subtree<Span>, Option<&'a TokenTree<Span>>))>, Vec<Entry<'a, Span>>) + { // Must contain everything in tokens and then the Entry::End let mut entries = vec![]; let mut children = vec![]; @@ -64,25 +77,25 @@ impl<'a> TokenList<'a> for &'a Subtree { } } -impl<'t> TokenBuffer<'t> { - pub fn from_tokens(tokens: &'t [TokenTree]) -> TokenBuffer<'t> { +impl<'t, Span> TokenBuffer<'t, Span> { + pub fn from_tokens(tokens: &'t [TokenTree<Span>]) -> TokenBuffer<'t, Span> { Self::new(tokens) } - pub fn from_subtree(subtree: &'t Subtree) -> TokenBuffer<'t> { + pub fn from_subtree(subtree: &'t Subtree<Span>) -> TokenBuffer<'t, Span> { Self::new(subtree) } - fn new<T: TokenList<'t>>(tokens: T) -> TokenBuffer<'t> { + fn new<T: TokenList<'t, Span>>(tokens: T) -> TokenBuffer<'t, Span> { let mut buffers = vec![]; let idx = TokenBuffer::new_inner(tokens, &mut buffers, None); assert_eq!(idx, 0); TokenBuffer { buffers } } - fn new_inner<T: TokenList<'t>>( + fn new_inner<T: TokenList<'t, Span>>( tokens: T, - buffers: &mut Vec<Box<[Entry<'t>]>>, + buffers: &mut Vec<Box<[Entry<'t, Span>]>>, next: Option<EntryPtr>, ) -> usize { let (children, mut entries) = tokens.entries(); @@ -105,25 +118,25 @@ impl<'t> TokenBuffer<'t> { /// Creates a cursor referencing the first token in the buffer and able to /// traverse until the end of the buffer. - pub fn begin(&self) -> Cursor<'_> { + pub fn begin(&self) -> Cursor<'_, Span> { Cursor::create(self, EntryPtr(EntryId(0), 0)) } - fn entry(&self, ptr: &EntryPtr) -> Option<&Entry<'_>> { + fn entry(&self, ptr: &EntryPtr) -> Option<&Entry<'_, Span>> { let id = ptr.0; self.buffers[id.0].get(ptr.1) } } #[derive(Debug)] -pub enum TokenTreeRef<'a> { - Subtree(&'a Subtree, Option<&'a TokenTree>), - Leaf(&'a Leaf, &'a TokenTree), +pub enum TokenTreeRef<'a, Span> { + Subtree(&'a Subtree<Span>, Option<&'a TokenTree<Span>>), + Leaf(&'a Leaf<Span>, &'a TokenTree<Span>), } -impl<'a> TokenTreeRef<'a> { - pub fn cloned(&self) -> TokenTree { - match &self { +impl<'a, Span: Clone> TokenTreeRef<'a, Span> { + pub fn cloned(&self) -> TokenTree<Span> { + match self { TokenTreeRef::Subtree(subtree, tt) => match tt { Some(it) => (*it).clone(), None => (*subtree).clone().into(), @@ -135,20 +148,20 @@ impl<'a> TokenTreeRef<'a> { /// A safe version of `Cursor` from `syn` crate <https://github.com/dtolnay/syn/blob/6533607f91686545cb034d2838beea338d9d0742/src/buffer.rs#L125> #[derive(Copy, Clone, Debug)] -pub struct Cursor<'a> { - buffer: &'a TokenBuffer<'a>, +pub struct Cursor<'a, Span> { + buffer: &'a TokenBuffer<'a, Span>, ptr: EntryPtr, } -impl<'a> PartialEq for Cursor<'a> { - fn eq(&self, other: &Cursor<'_>) -> bool { +impl<'a, Span> PartialEq for Cursor<'a, Span> { + fn eq(&self, other: &Cursor<'_, Span>) -> bool { self.ptr == other.ptr && std::ptr::eq(self.buffer, other.buffer) } } -impl<'a> Eq for Cursor<'a> {} +impl<'a, Span> Eq for Cursor<'a, Span> {} -impl<'a> Cursor<'a> { +impl<'a, Span> Cursor<'a, Span> { /// Check whether it is eof pub fn eof(self) -> bool { matches!(self.buffer.entry(&self.ptr), None | Some(Entry::End(None))) @@ -156,7 +169,7 @@ impl<'a> Cursor<'a> { /// If the cursor is pointing at the end of a subtree, returns /// the parent subtree - pub fn end(self) -> Option<&'a Subtree> { + pub fn end(self) -> Option<&'a Subtree<Span>> { match self.entry() { Some(Entry::End(Some(ptr))) => { let idx = ptr.1; @@ -171,13 +184,13 @@ impl<'a> Cursor<'a> { } } - fn entry(self) -> Option<&'a Entry<'a>> { + fn entry(&self) -> Option<&'a Entry<'a, Span>> { self.buffer.entry(&self.ptr) } /// If the cursor is pointing at a `Subtree`, returns /// a cursor into that subtree - pub fn subtree(self) -> Option<Cursor<'a>> { + pub fn subtree(self) -> Option<Cursor<'a, Span>> { match self.entry() { Some(Entry::Subtree(_, _, entry_id)) => { Some(Cursor::create(self.buffer, EntryPtr(*entry_id, 0))) @@ -187,7 +200,7 @@ impl<'a> Cursor<'a> { } /// If the cursor is pointing at a `TokenTree`, returns it - pub fn token_tree(self) -> Option<TokenTreeRef<'a>> { + pub fn token_tree(self) -> Option<TokenTreeRef<'a, Span>> { match self.entry() { Some(Entry::Leaf(tt)) => match tt { TokenTree::Leaf(leaf) => Some(TokenTreeRef::Leaf(leaf, tt)), @@ -198,12 +211,12 @@ impl<'a> Cursor<'a> { } } - fn create(buffer: &'a TokenBuffer<'_>, ptr: EntryPtr) -> Cursor<'a> { + fn create(buffer: &'a TokenBuffer<'_, Span>, ptr: EntryPtr) -> Cursor<'a, Span> { Cursor { buffer, ptr } } /// Bump the cursor - pub fn bump(self) -> Cursor<'a> { + pub fn bump(self) -> Cursor<'a, Span> { if let Some(Entry::End(exit)) = self.buffer.entry(&self.ptr) { match exit { Some(exit) => Cursor::create(self.buffer, *exit), @@ -216,10 +229,16 @@ impl<'a> Cursor<'a> { /// Bump the cursor, if it is a subtree, returns /// a cursor into that subtree - pub fn bump_subtree(self) -> Cursor<'a> { + pub fn bump_subtree(self) -> Cursor<'a, Span> { match self.entry() { - Some(Entry::Subtree(_, _, _)) => self.subtree().unwrap(), - _ => self.bump(), + Some(&Entry::Subtree(_, _, entry_id)) => { + Cursor::create(self.buffer, EntryPtr(entry_id, 0)) + } + Some(Entry::End(exit)) => match exit { + Some(exit) => Cursor::create(self.buffer, *exit), + None => self, + }, + _ => Cursor::create(self.buffer, EntryPtr(self.ptr.0, self.ptr.1 + 1)), } } diff --git a/src/tools/rust-analyzer/crates/tt/src/lib.rs b/src/tools/rust-analyzer/crates/tt/src/lib.rs index 353b09fd8..b7dbc82e1 100644 --- a/src/tools/rust-analyzer/crates/tt/src/lib.rs +++ b/src/tools/rust-analyzer/crates/tt/src/lib.rs @@ -16,45 +16,106 @@ pub use smol_str::SmolStr; /// which source tokens. We do it by assigning an distinct identity to each /// source token and making sure that identities are preserved during macro /// expansion. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct TokenId(pub u32); +impl fmt::Debug for TokenId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + impl TokenId { + pub const UNSPECIFIED: TokenId = TokenId(!0); pub const fn unspecified() -> TokenId { - TokenId(!0) + Self::UNSPECIFIED } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum TokenTree { - Leaf(Leaf), - Subtree(Subtree), -} -impl_from!(Leaf, Subtree for TokenTree); +pub mod token_id { + pub use crate::{DelimiterKind, Spacing, TokenId}; + pub type Span = crate::TokenId; + pub type Subtree = crate::Subtree<Span>; + pub type Punct = crate::Punct<Span>; + pub type Delimiter = crate::Delimiter<Span>; + pub type Leaf = crate::Leaf<Span>; + pub type Ident = crate::Ident<Span>; + pub type Literal = crate::Literal<Span>; + pub type TokenTree = crate::TokenTree<Span>; + pub mod buffer { + pub type TokenBuffer<'a> = crate::buffer::TokenBuffer<'a, super::Span>; + pub type Cursor<'a> = crate::buffer::Cursor<'a, super::Span>; + pub type TokenTreeRef<'a> = crate::buffer::TokenTreeRef<'a, super::Span>; + } -impl TokenTree { - pub fn empty() -> Self { - TokenTree::Subtree(Subtree::default()) + impl Delimiter { + pub const UNSPECIFIED: Self = Self { + open: TokenId::UNSPECIFIED, + close: TokenId::UNSPECIFIED, + kind: DelimiterKind::Invisible, + }; + pub const fn unspecified() -> Self { + Self::UNSPECIFIED + } + } + impl Subtree { + pub const fn empty() -> Self { + Subtree { delimiter: Delimiter::unspecified(), token_trees: vec![] } + } + } + impl TokenTree { + pub const fn empty() -> Self { + Self::Subtree(Subtree { delimiter: Delimiter::unspecified(), token_trees: vec![] }) + } } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct SyntaxContext(pub u32); + +// #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +// pub struct Span { +// pub id: TokenId, +// pub ctx: SyntaxContext, +// } +// pub type Span = (TokenId, SyntaxContext); + #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Leaf { - Literal(Literal), - Punct(Punct), - Ident(Ident), +pub enum TokenTree<Span> { + Leaf(Leaf<Span>), + Subtree(Subtree<Span>), } -impl_from!(Literal, Punct, Ident for Leaf); +impl_from!(Leaf<Span>, Subtree<Span> for TokenTree); -#[derive(Clone, PartialEq, Eq, Hash, Default)] -pub struct Subtree { - pub delimiter: Option<Delimiter>, - pub token_trees: Vec<TokenTree>, +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Leaf<Span> { + Literal(Literal<Span>), + Punct(Punct<Span>), + Ident(Ident<Span>), +} + +impl<Span> Leaf<Span> { + pub fn span(&self) -> &Span { + match self { + Leaf::Literal(it) => &it.span, + Leaf::Punct(it) => &it.span, + Leaf::Ident(it) => &it.span, + } + } +} +impl_from!(Literal<Span>, Punct<Span>, Ident<Span> for Leaf); + +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct Subtree<Span> { + // FIXME, this should not be Option + pub delimiter: Delimiter<Span>, + pub token_trees: Vec<TokenTree<Span>>, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct Delimiter { - pub id: TokenId, +pub struct Delimiter<Span> { + pub open: Span, + pub close: Span, pub kind: DelimiterKind, } @@ -63,19 +124,20 @@ pub enum DelimiterKind { Parenthesis, Brace, Bracket, + Invisible, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Literal { +pub struct Literal<Span> { pub text: SmolStr, - pub id: TokenId, + pub span: Span, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Punct { +pub struct Punct<Span> { pub char: char, pub spacing: Spacing, - pub id: TokenId, + pub span: Span, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -85,39 +147,25 @@ pub enum Spacing { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Ident { - /// Identifier or keyword. Unlike rustc, we keep "r#" prefix when it represents a raw identifier. +/// Identifier or keyword. Unlike rustc, we keep "r#" prefix when it represents a raw identifier. +pub struct Ident<Span> { pub text: SmolStr, - pub id: TokenId, + pub span: Span, } -impl Ident { - /// Constructor intended to be used only by proc macro server. `text` should not contain raw - /// identifier prefix. - pub fn new_with_is_raw(text: SmolStr, id: TokenId, is_raw: bool) -> Self { - let text = if is_raw { SmolStr::from_iter(["r#", &text]) } else { text }; - Ident { text, id } - } -} - -impl Leaf { - pub fn id(&self) -> TokenId { - match self { - Leaf::Literal(l) => l.id, - Leaf::Punct(p) => p.id, - Leaf::Ident(i) => i.id, - } - } -} - -fn print_debug_subtree(f: &mut fmt::Formatter<'_>, subtree: &Subtree, level: usize) -> fmt::Result { +fn print_debug_subtree<Span: fmt::Debug>( + f: &mut fmt::Formatter<'_>, + subtree: &Subtree<Span>, + level: usize, +) -> fmt::Result { let align = " ".repeat(level); - let aux = match subtree.delimiter.map(|it| (it.kind, it.id.0)) { - None => "$".to_string(), - Some((DelimiterKind::Parenthesis, id)) => format!("() {id}"), - Some((DelimiterKind::Brace, id)) => format!("{{}} {id}"), - Some((DelimiterKind::Bracket, id)) => format!("[] {id}"), + let Delimiter { kind, open, close } = &subtree.delimiter; + let aux = match kind { + DelimiterKind::Invisible => format!("$$ {:?} {:?}", open, close), + DelimiterKind::Parenthesis => format!("() {:?} {:?}", open, close), + DelimiterKind::Brace => format!("{{}} {:?} {:?}", open, close), + DelimiterKind::Bracket => format!("[] {:?} {:?}", open, close), }; if subtree.token_trees.is_empty() { @@ -135,21 +183,25 @@ fn print_debug_subtree(f: &mut fmt::Formatter<'_>, subtree: &Subtree, level: usi Ok(()) } -fn print_debug_token(f: &mut fmt::Formatter<'_>, tkn: &TokenTree, level: usize) -> fmt::Result { +fn print_debug_token<Span: fmt::Debug>( + f: &mut fmt::Formatter<'_>, + tkn: &TokenTree<Span>, + level: usize, +) -> fmt::Result { let align = " ".repeat(level); match tkn { TokenTree::Leaf(leaf) => match leaf { - Leaf::Literal(lit) => write!(f, "{align}LITERAL {} {}", lit.text, lit.id.0)?, + Leaf::Literal(lit) => write!(f, "{}LITERAL {} {:?}", align, lit.text, lit.span)?, Leaf::Punct(punct) => write!( f, - "{}PUNCH {} [{}] {}", + "{}PUNCH {} [{}] {:?}", align, punct.char, if punct.spacing == Spacing::Alone { "alone" } else { "joint" }, - punct.id.0 + punct.span )?, - Leaf::Ident(ident) => write!(f, "{align}IDENT {} {}", ident.text, ident.id.0)?, + Leaf::Ident(ident) => write!(f, "{}IDENT {} {:?}", align, ident.text, ident.span)?, }, TokenTree::Subtree(subtree) => { print_debug_subtree(f, subtree, level)?; @@ -159,13 +211,13 @@ fn print_debug_token(f: &mut fmt::Formatter<'_>, tkn: &TokenTree, level: usize) Ok(()) } -impl fmt::Debug for Subtree { +impl<Span: fmt::Debug> fmt::Debug for Subtree<Span> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { print_debug_subtree(f, self, 0) } } -impl fmt::Display for TokenTree { +impl<Span> fmt::Display for TokenTree<Span> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { TokenTree::Leaf(it) => fmt::Display::fmt(it, f), @@ -174,13 +226,13 @@ impl fmt::Display for TokenTree { } } -impl fmt::Display for Subtree { +impl<Span> fmt::Display for Subtree<Span> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let (l, r) = match self.delimiter_kind() { - Some(DelimiterKind::Parenthesis) => ("(", ")"), - Some(DelimiterKind::Brace) => ("{", "}"), - Some(DelimiterKind::Bracket) => ("[", "]"), - None => ("", ""), + let (l, r) = match self.delimiter.kind { + DelimiterKind::Parenthesis => ("(", ")"), + DelimiterKind::Brace => ("{", "}"), + DelimiterKind::Bracket => ("[", "]"), + DelimiterKind::Invisible => ("", ""), }; f.write_str(l)?; let mut needs_space = false; @@ -202,7 +254,7 @@ impl fmt::Display for Subtree { } } -impl fmt::Display for Leaf { +impl<Span> fmt::Display for Leaf<Span> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Leaf::Ident(it) => fmt::Display::fmt(it, f), @@ -212,25 +264,25 @@ impl fmt::Display for Leaf { } } -impl fmt::Display for Ident { +impl<Span> fmt::Display for Ident<Span> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.text, f) } } -impl fmt::Display for Literal { +impl<Span> fmt::Display for Literal<Span> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.text, f) } } -impl fmt::Display for Punct { +impl<Span> fmt::Display for Punct<Span> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.char, f) } } -impl Subtree { +impl<Span> Subtree<Span> { /// Count the number of tokens recursively pub fn count(&self) -> usize { let children_count = self @@ -244,20 +296,16 @@ impl Subtree { self.token_trees.len() + children_count } - - pub fn delimiter_kind(&self) -> Option<DelimiterKind> { - self.delimiter.map(|it| it.kind) - } } -impl Subtree { +impl<Span> Subtree<Span> { /// A simple line string used for debugging pub fn as_debug_string(&self) -> String { - let delim = match self.delimiter_kind() { - Some(DelimiterKind::Brace) => ("{", "}"), - Some(DelimiterKind::Bracket) => ("[", "]"), - Some(DelimiterKind::Parenthesis) => ("(", ")"), - None => (" ", " "), + let delim = match self.delimiter.kind { + DelimiterKind::Brace => ("{", "}"), + DelimiterKind::Bracket => ("[", "]"), + DelimiterKind::Parenthesis => ("(", ")"), + DelimiterKind::Invisible => ("$", "$"), }; let mut res = String::new(); @@ -275,7 +323,7 @@ impl Subtree { (Leaf::Ident(_), Some(&TokenTree::Leaf(Leaf::Ident(_)))) => { " ".to_string() + &s } - (Leaf::Punct(_), Some(&TokenTree::Leaf(Leaf::Punct(punct)))) => { + (Leaf::Punct(_), Some(TokenTree::Leaf(Leaf::Punct(punct)))) => { if punct.spacing == Spacing::Alone { " ".to_string() + &s } else { @@ -298,19 +346,19 @@ impl Subtree { pub mod buffer; -pub fn pretty(tkns: &[TokenTree]) -> String { - fn tokentree_to_text(tkn: &TokenTree) -> String { +pub fn pretty<Span>(tkns: &[TokenTree<Span>]) -> String { + fn tokentree_to_text<Span>(tkn: &TokenTree<Span>) -> String { match tkn { TokenTree::Leaf(Leaf::Ident(ident)) => ident.text.clone().into(), TokenTree::Leaf(Leaf::Literal(literal)) => literal.text.clone().into(), TokenTree::Leaf(Leaf::Punct(punct)) => format!("{}", punct.char), TokenTree::Subtree(subtree) => { let content = pretty(&subtree.token_trees); - let (open, close) = match subtree.delimiter.map(|it| it.kind) { - None => ("", ""), - Some(DelimiterKind::Brace) => ("{", "}"), - Some(DelimiterKind::Parenthesis) => ("(", ")"), - Some(DelimiterKind::Bracket) => ("[", "]"), + let (open, close) = match subtree.delimiter.kind { + DelimiterKind::Brace => ("{", "}"), + DelimiterKind::Bracket => ("[", "]"), + DelimiterKind::Parenthesis => ("(", ")"), + DelimiterKind::Invisible => ("", ""), }; format!("{open}{content}{close}") } diff --git a/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml b/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml index 061f3c157..e06b98d81 100644 --- a/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml +++ b/src/tools/rust-analyzer/crates/vfs-notify/Cargo.toml @@ -2,9 +2,11 @@ name = "vfs-notify" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -16,5 +18,5 @@ walkdir = "2.3.2" crossbeam-channel = "0.5.5" notify = "5.0" -vfs = { path = "../vfs", version = "0.0.0" } -paths = { path = "../paths", version = "0.0.0" } +vfs.workspace = true +paths.workspace = true diff --git a/src/tools/rust-analyzer/crates/vfs/Cargo.toml b/src/tools/rust-analyzer/crates/vfs/Cargo.toml index e55bf6f29..802a30006 100644 --- a/src/tools/rust-analyzer/crates/vfs/Cargo.toml +++ b/src/tools/rust-analyzer/crates/vfs/Cargo.toml @@ -2,9 +2,11 @@ name = "vfs" version = "0.0.0" description = "TBD" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.65" + +authors.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true [lib] doctest = false @@ -14,5 +16,5 @@ rustc-hash = "1.1.0" fst = "0.4.7" indexmap = "1.9.1" -paths = { path = "../paths", version = "0.0.0" } -stdx = { path = "../stdx", version = "0.0.0" } +paths.workspace = true +stdx.workspace = true diff --git a/src/tools/rust-analyzer/crates/vfs/src/lib.rs b/src/tools/rust-analyzer/crates/vfs/src/lib.rs index c61f30387..14972d290 100644 --- a/src/tools/rust-analyzer/crates/vfs/src/lib.rs +++ b/src/tools/rust-analyzer/crates/vfs/src/lib.rs @@ -75,6 +75,7 @@ pub struct Vfs { } /// Changed file in the [`Vfs`]. +#[derive(Debug)] pub struct ChangedFile { /// Id of the changed file pub file_id: FileId, @@ -161,9 +162,9 @@ impl Vfs { let file_id = self.alloc_file_id(path); let change_kind = match (&self.get(file_id), &contents) { (None, None) => return false, + (Some(old), Some(new)) if old == new => return false, (None, Some(_)) => ChangeKind::Create, (Some(_), None) => ChangeKind::Delete, - (Some(old), Some(new)) if old == new => return false, (Some(_), Some(_)) => ChangeKind::Modify, }; diff --git a/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs b/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs index b23c9f196..38501a8ba 100644 --- a/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs +++ b/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs @@ -1,7 +1,7 @@ //! Abstract-ish representation of paths for VFS. use std::fmt; -use paths::{AbsPath, AbsPathBuf}; +use paths::{AbsPath, AbsPathBuf, RelPath}; /// Path in [`Vfs`]. /// @@ -84,6 +84,14 @@ impl VfsPath { } } + pub fn strip_prefix(&self, other: &VfsPath) -> Option<&RelPath> { + match (&self.0, &other.0) { + (VfsPathRepr::PathBuf(lhs), VfsPathRepr::PathBuf(rhs)) => lhs.strip_prefix(rhs), + (VfsPathRepr::VirtualPath(lhs), VfsPathRepr::VirtualPath(rhs)) => lhs.strip_prefix(rhs), + (VfsPathRepr::PathBuf(_) | VfsPathRepr::VirtualPath(_), _) => None, + } + } + /// Returns the `VfsPath` without its final component, if there is one. /// /// Returns [`None`] if the path is a root or prefix. @@ -320,6 +328,13 @@ impl VirtualPath { self.0.starts_with(&other.0) } + fn strip_prefix(&self, base: &VirtualPath) -> Option<&RelPath> { + <_ as AsRef<std::path::Path>>::as_ref(&self.0) + .strip_prefix(&base.0) + .ok() + .map(RelPath::new_unchecked) + } + /// Remove the last component of `self`. /// /// This will find the last `'/'` in `self`, and remove everything after it, diff --git a/src/tools/rust-analyzer/docs/dev/architecture.md b/src/tools/rust-analyzer/docs/dev/architecture.md index a07cf036e..895de5798 100644 --- a/src/tools/rust-analyzer/docs/dev/architecture.md +++ b/src/tools/rust-analyzer/docs/dev/architecture.md @@ -119,7 +119,7 @@ See [#93](https://github.com/rust-lang/rust-analyzer/pull/93) for an example PR **Architecture Invariant:** `syntax` crate is completely independent from the rest of rust-analyzer. It knows nothing about salsa or LSP. This is important because it is possible to make useful tooling using only the syntax tree. Without semantic information, you don't need to be able to _build_ code, which makes the tooling more robust. -See also https://web.stanford.edu/~mlfbrown/paper.pdf. +See also https://mlfbrown.com/paper.pdf. You can view the `syntax` crate as an entry point to rust-analyzer. `syntax` crate is an **API Boundary**. diff --git a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md index a4780af1a..c3623a5cc 100644 --- a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md +++ b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@ <!--- -lsp_ext.rs hash: 45bd7985265725c5 +lsp_ext.rs hash: d87477896dfe41d4 If you need to change the above hash to make the test pass, please check if you need to adjust this doc as well and ping this issue: diff --git a/src/tools/rust-analyzer/docs/user/generated_config.adoc b/src/tools/rust-analyzer/docs/user/generated_config.adoc index b33a2e795..50e3670a7 100644 --- a/src/tools/rust-analyzer/docs/user/generated_config.adoc +++ b/src/tools/rust-analyzer/docs/user/generated_config.adoc @@ -99,6 +99,14 @@ Unsetting this disables sysroot loading. This option does not take effect until rust-analyzer is restarted. -- +[[rust-analyzer.cargo.sysrootSrc]]rust-analyzer.cargo.sysrootSrc (default: `null`):: ++ +-- +Relative path to the sysroot library sources. If left unset, this will default to +`{cargo.sysroot}/lib/rustlib/src/rust/library`. + +This option does not take effect until rust-analyzer is restarted. +-- [[rust-analyzer.cargo.target]]rust-analyzer.cargo.target (default: `null`):: + -- @@ -219,6 +227,11 @@ with `self` prefixed to them when inside a method. -- Whether to add parenthesis and argument snippets when completing function. -- +[[rust-analyzer.completion.limit]]rust-analyzer.completion.limit (default: `null`):: ++ +-- +Maximum number of completions to return. If `None`, the limit is infinite. +-- [[rust-analyzer.completion.postfix.enable]]rust-analyzer.completion.postfix.enable (default: `true`):: + -- @@ -486,11 +499,6 @@ Whether to show inlay type hints for elided lifetimes in function signatures. -- Whether to prefer using parameter names as the name for elided lifetime hints if possible. -- -[[rust-analyzer.inlayHints.locationLinks]]rust-analyzer.inlayHints.locationLinks (default: `true`):: -+ --- -Whether to use location links for parts of type mentioned in inlay hints. --- [[rust-analyzer.inlayHints.maxLength]]rust-analyzer.inlayHints.maxLength (default: `25`):: + -- diff --git a/src/tools/rust-analyzer/docs/user/manual.adoc b/src/tools/rust-analyzer/docs/user/manual.adoc index 1a4c70575..cb96feeb5 100644 --- a/src/tools/rust-analyzer/docs/user/manual.adoc +++ b/src/tools/rust-analyzer/docs/user/manual.adoc @@ -232,18 +232,66 @@ The `rust-analyzer` binary can be installed via https://brew.sh/[Homebrew]. $ brew install rust-analyzer ---- -=== Emacs +=== VS Code or VSCodium in Flatpak + +Setting up `rust-analyzer` with a Flatpak version of Code is not trivial because of the Flatpak sandbox. +While the sandbox can be disabled for some directories, `/usr/bin` will always be mounted under `/run/host/usr/bin`. +This prevents access to the system's C compiler, a system-wide installation of Rust, or any other libraries you might want to link to. +Some compilers and libraries can be acquired as Flatpak SDKs, such as `org.freedesktop.Sdk.Extension.rust-stable` or `org.freedesktop.Sdk.Extension.llvm15`. + +If you use a Flatpak SDK for Rust, there should be no extra steps necessary. -Note this excellent https://robert.kra.hn/posts/2021-02-07_rust-with-emacs/[guide] from https://github.com/rksm[@rksm]. +If you want to use Flatpak in combination with `rustup`, the following steps might help: + + - both Rust and `rustup` have to be installed using https://rustup.rs. Distro packages _will not_ work. + - you need to launch Code, open a terminal and run `echo $PATH` + - using https://flathub.org/apps/details/com.github.tchx84.Flatseal[Flatseal], you must add an environment variable called `PATH`. + Set its value to the output from above, appending `:~/.cargo/bin`, where `~` is the path to your home directory. + You must replace `~`, as it won't be expanded otherwise. + - while Flatseal is open, you must enable access to "All user files" + +A C compiler should already be available via `org.freedesktop.Sdk`. +Any other tools or libraries you will need to acquire from Flatpak. + +=== Emacs Prerequisites: You have installed the <<rust-analyzer-language-server-binary,`rust-analyzer` binary>>. -Emacs support is maintained as part of the https://github.com/emacs-lsp/lsp-mode[Emacs-LSP] package in https://github.com/emacs-lsp/lsp-mode/blob/master/lsp-rust.el[lsp-rust.el]. +To use `rust-analyzer`, you need to install and enable one of the two popular two popular LSP client implementations for Emacs, https://github.com/joaotavora/eglot[Eglot] or https://github.com/emacs-lsp/lsp-mode[LSP Mode]. Both enable `rust-analyzer` by default in rust buffers if it is available. + +==== Eglot + +Eglot is the more minimalistic and lightweight LSP client for Emacs, integrates well with existing Emacs functionality and will be built into Emacs starting from release 29. + +After installing Eglot, e.g. via `M-x package-install` (not needed from Emacs 29), you can enable it via the `M-x eglot` command or load it automatically in `rust-mode` via + +[source,emacs-lisp] +---- +(add-hook 'rust-mode-hook 'eglot-ensure) +---- + +For more detailed instructions and options see the https://joaotavora.github.io/eglot[Eglot manual] (also available from Emacs via `M-x info`) and the +https://github.com/joaotavora/eglot/blob/master/README.md[Eglot readme]. + +Eglot does not support the rust-analyzer extensions to the language-server protocol and does not aim to do so in the future. The https://github.com/nemethf/eglot-x#rust-analyzer-extensions[eglot-x] package adds experimental support for those LSP extensions. + +==== LSP Mode + +LSP-mode is the original LSP-client for emacs. Compared to Eglot it has a larger codebase and supports more features, like LSP protocol extensions. +With extension packages like https://github.com/emacs-lsp/lsp-mode[LSP UI] it offers a lot of visual eyecandy. +Further it integrates well with https://github.com/emacs-lsp/dap-mode[DAP mode] for support of the Debug Adapter Protocol. + +You can install LSP-mode via `M-x package-install` and then run it via the `M-x lsp` command or load it automatically in rust buffers with + +[source,emacs-lisp] +---- +(add-hook 'rust-mode-hook 'lsp-deferred) +---- + +For more information on how to set up LSP mode and its extension package see the instructions in the https://emacs-lsp.github.io/lsp-mode/page/installation[LSP mode manual]. +Also see the https://emacs-lsp.github.io/lsp-mode/page/lsp-rust-analyzer/[rust-analyzer section] for `rust-analyzer` specific options and commands, which you can optionally bind to keys. -1. Install the most recent version of `emacs-lsp` package by following the https://github.com/emacs-lsp/lsp-mode[Emacs-LSP instructions]. -2. Set `lsp-rust-server` to `'rust-analyzer`. -3. Run `lsp` in a Rust buffer. -4. (Optionally) bind commands like `lsp-rust-analyzer-join-lines`, `lsp-extend-selection` and `lsp-rust-analyzer-expand-macro` to keys. +Note the excellent https://robert.kra.hn/posts/2021-02-07_rust-with-emacs/[guide] from https://github.com/rksm[@rksm] on how to set-up Emacs for Rust development with LSP mode and several other packages. === Vim/NeoVim @@ -487,6 +535,47 @@ https://docs.helix-editor.com/[Helix] supports LSP by default. However, it won't install `rust-analyzer` automatically. You can follow instructions for installing <<rust-analyzer-language-server-binary,`rust-analyzer` binary>>. +=== Visual Studio 2022 + +There are multiple rust-analyzer extensions for Visual Studio 2022 on Windows: + +==== rust-analyzer.vs + +(License: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International) + +https://marketplace.visualstudio.com/items?itemName=kitamstudios.RustAnalyzer[Visual Studio Marketplace] + +https://github.com/kitamstudios/rust-analyzer/[GitHub] + +Support for Rust development in the Visual Studio IDE is enabled by the link:https://marketplace.visualstudio.com/items?itemName=kitamstudios.RustAnalyzer[rust-analyzer] package. Either click on the download link or install from IDE's extension manager. +For now link:https://visualstudio.microsoft.com/downloads/[Visual Studio 2022] is required. All editions are supported viz. Community, Professional & Enterprise. +The package aims to provide 0-friction installation and therefore comes loaded with most things required including rust-analyzer binary. If anything it needs is missing, appropriate errors / warnings will guide the user. E.g. cargo.exe needs to be in path and the package will tell you as much. +This package is under rapid active development. So if you encounter any issues please file it at link:https://github.com/kitamstudios/rust-analyzer/[rust-analyzer.vs]. + +==== VS_RustAnalyzer + +(License: GPL) + +https://marketplace.visualstudio.com/items?itemName=cchharris.vsrustanalyzer[Visual Studio Marketplace] + +https://github.com/cchharris/VS-RustAnalyzer[GitHub] + +==== SourceGear Rust + +(License: closed source) + +https://marketplace.visualstudio.com/items?itemName=SourceGear.SourceGearRust[Visual Studio Marketplace] + +https://github.com/sourcegear/rust-vs-extension[GitHub (docs, issues, discussions)] + +* Free (no-cost) +* Supports all editions of Visual Studio 2022 on Windows: Community, Professional, or Enterprise + +=== Lapce + +https://lapce.dev/[Lapce] has a Rust plugin which you can install directly. +Unfortunately, it downloads an old version of `rust-analyzer`, but you can set the server path under Settings. + === Crates There is a package named `ra_ap_rust_analyzer` available on https://crates.io/crates/ra_ap_rust-analyzer[crates.io], for someone who wants to use it programmatically. @@ -684,14 +773,18 @@ See https://github.com/rust-analyzer/rust-project.json-example for a small examp You can set the `RA_LOG` environment variable to `rust_analyzer=info` to inspect how rust-analyzer handles config and project loading. -Note that calls to `cargo check` are disabled when using `rust-project.json` by default, so compilation errors and warnings will no longer be sent to your LSP client. To enable these compilation errors you will need to specify explicitly what command rust-analyzer should run to perform the checks using the `checkOnSave.overrideCommand` configuration. As an example, the following configuration explicitly sets `cargo check` as the `checkOnSave` command. +Note that calls to `cargo check` are disabled when using `rust-project.json` by default, so compilation errors and warnings will no longer be sent to your LSP client. +To enable these compilation errors you will need to specify explicitly what command rust-analyzer should run to perform the checks using the `rust-analyzer.check.overrideCommand` configuration. +As an example, the following configuration explicitly sets `cargo check` as the `check` command. [source,json] ---- -{ "rust-analyzer.checkOnSave.overrideCommand": ["cargo", "check", "--message-format=json"] } +{ "rust-analyzer.check.overrideCommand": ["cargo", "check", "--message-format=json"] } ---- -The `checkOnSave.overrideCommand` requires the command specified to output json error messages for rust-analyzer to consume. The `--message-format=json` flag does this for `cargo check` so whichever command you use must also output errors in this format. See the <<Configuration>> section for more information. +`check.overrideCommand` requires the command specified to output json error messages for rust-analyzer to consume. +The `--message-format=json` flag does this for `cargo check` so whichever command you use must also output errors in this format. +See the <<Configuration>> section for more information. == Security @@ -732,6 +825,10 @@ include::./generated_assists.adoc[] While most errors and warnings provided by rust-analyzer come from the `cargo check` integration, there's a growing number of diagnostics implemented using rust-analyzer's own analysis. Some of these diagnostics don't respect `\#[allow]` or `\#[deny]` attributes yet, but can be turned off using the `rust-analyzer.diagnostics.enable`, `rust-analyzer.diagnostics.experimental.enable` or `rust-analyzer.diagnostics.disabled` settings. +=== Clippy + +To run `cargo clippy` instead of `cargo check`, you can set `"rust-analyzer.check.command": "clippy"`. + include::./generated_diagnostic.adoc[] == Editor Features @@ -864,7 +961,7 @@ Also note that a full runnable name is something like *run bin_or_example_name*, Instead of relying on the built-in `cargo check`, you can configure Code to run a command in the background and use the `$rustc-watch` problem matcher to generate inline error markers from its output. -To do this you need to create a new https://code.visualstudio.com/docs/editor/tasks[VS Code Task] and set `rust-analyzer.checkOnSave.enable: false` in preferences. +To do this you need to create a new https://code.visualstudio.com/docs/editor/tasks[VS Code Task] and set `"rust-analyzer.checkOnSave": false` in preferences. For example, if you want to run https://crates.io/crates/cargo-watch[`cargo watch`] instead, you might add the following to `.vscode/tasks.json`: diff --git a/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml b/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml index 9bba9e87e..6e32e3960 100644 --- a/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml +++ b/src/tools/rust-analyzer/lib/lsp-server/Cargo.toml @@ -13,4 +13,4 @@ serde = { version = "1.0.144", features = ["derive"] } crossbeam-channel = "0.5.6" [dev-dependencies] -lsp-types = "=0.93.2" +lsp-types = "=0.94" diff --git a/src/tools/rust-analyzer/lib/lsp-server/src/stdio.rs b/src/tools/rust-analyzer/lib/lsp-server/src/stdio.rs index cdee6432d..49a825e57 100644 --- a/src/tools/rust-analyzer/lib/lsp-server/src/stdio.rs +++ b/src/tools/rust-analyzer/lib/lsp-server/src/stdio.rs @@ -21,10 +21,7 @@ pub(crate) fn stdio_transport() -> (Sender<Message>, Receiver<Message>, IoThread let stdin = stdin(); let mut stdin = stdin.lock(); while let Some(msg) = Message::read(&mut stdin)? { - let is_exit = match &msg { - Message::Notification(n) => n.is_exit(), - _ => false, - }; + let is_exit = matches!(&msg, Message::Notification(n) if n.is_exit()); reader_sender.send(msg).unwrap(); diff --git a/src/tools/rust-analyzer/xtask/Cargo.toml b/src/tools/rust-analyzer/xtask/Cargo.toml index 2dd01796c..b4b294c30 100644 --- a/src/tools/rust-analyzer/xtask/Cargo.toml +++ b/src/tools/rust-analyzer/xtask/Cargo.toml @@ -12,5 +12,6 @@ flate2 = "1.0.24" write-json = "0.1.2" xshell = "0.2.2" xflags = "0.3.0" +time = { version = "0.3", default-features = false } zip = { version = "0.6", default-features = false, features = ["deflate", "time"] } # Avoid adding more dependencies to this crate diff --git a/src/tools/rust-analyzer/xtask/src/dist.rs b/src/tools/rust-analyzer/xtask/src/dist.rs index 74715c53e..5a03c71b2 100644 --- a/src/tools/rust-analyzer/xtask/src/dist.rs +++ b/src/tools/rust-analyzer/xtask/src/dist.rs @@ -6,6 +6,7 @@ use std::{ }; use flate2::{write::GzEncoder, Compression}; +use time::OffsetDateTime; use xshell::{cmd, Shell}; use zip::{write::FileOptions, DateTime, ZipWriter}; @@ -112,7 +113,8 @@ fn zip(src_path: &Path, symbols_path: Option<&PathBuf>, dest_path: &Path) -> any src_path.file_name().unwrap().to_str().unwrap(), FileOptions::default() .last_modified_time( - DateTime::from_time(std::fs::metadata(src_path)?.modified()?.into()).unwrap(), + DateTime::try_from(OffsetDateTime::from(std::fs::metadata(src_path)?.modified()?)) + .unwrap(), ) .unix_permissions(0o755) .compression_method(zip::CompressionMethod::Deflated) @@ -125,7 +127,10 @@ fn zip(src_path: &Path, symbols_path: Option<&PathBuf>, dest_path: &Path) -> any symbols_path.file_name().unwrap().to_str().unwrap(), FileOptions::default() .last_modified_time( - DateTime::from_time(std::fs::metadata(src_path)?.modified()?.into()).unwrap(), + DateTime::try_from(OffsetDateTime::from( + std::fs::metadata(src_path)?.modified()?, + )) + .unwrap(), ) .compression_method(zip::CompressionMethod::Deflated) .compression_level(Some(9)), diff --git a/src/tools/rust-analyzer/xtask/src/publish.rs b/src/tools/rust-analyzer/xtask/src/publish.rs index 79b5f3d2f..cdb7d8fac 100644 --- a/src/tools/rust-analyzer/xtask/src/publish.rs +++ b/src/tools/rust-analyzer/xtask/src/publish.rs @@ -13,7 +13,7 @@ impl flags::PublishReleaseNotes { let tag_name = &file_name[0..10]; let original_changelog_url = create_original_changelog_url(&file_name); let additional_paragraph = - format!("\nSee also [original changelog]({original_changelog_url})."); + format!("\nSee also the [changelog post]({original_changelog_url})."); markdown.push_str(&additional_paragraph); if self.dry_run { println!("{markdown}"); diff --git a/src/tools/rust-analyzer/xtask/src/release/changelog.rs b/src/tools/rust-analyzer/xtask/src/release/changelog.rs index d2a1483e3..675384148 100644 --- a/src/tools/rust-analyzer/xtask/src/release/changelog.rs +++ b/src/tools/rust-analyzer/xtask/src/release/changelog.rs @@ -69,7 +69,7 @@ pub(crate) fn get_changelog( :page-layout: post Commit: commit:{commit}[] + -Release: release:{today}[] +Release: release:{today}[] (`TBD`) == New Features @@ -156,7 +156,7 @@ fn parse_title_line(s: &str) -> PrInfo { ("minor: ", PrKind::Skip), ]; - for &(prefix, kind) in &PREFIXES { + for (prefix, kind) in PREFIXES { if lower.starts_with(prefix) { let message = match &kind { PrKind::Skip => None, diff --git a/src/tools/rust-installer/src/generator.rs b/src/tools/rust-installer/src/generator.rs index 6a4cb9b4b..1e4d00b05 100644 --- a/src/tools/rust-installer/src/generator.rs +++ b/src/tools/rust-installer/src/generator.rs @@ -3,6 +3,7 @@ use super::Tarballer; use crate::compression::CompressionFormats; use crate::util::*; use anyhow::{bail, format_err, Context, Result}; +use std::collections::BTreeSet; use std::io::Write; use std::path::Path; @@ -121,13 +122,14 @@ impl Generator { /// Copies the `src` directory recursively to `dst`, writing `manifest.in` too. fn copy_and_manifest(src: &Path, dst: &Path, bulk_dirs: &str) -> Result<()> { - let manifest = create_new_file(dst.join("manifest.in"))?; + let mut manifest = create_new_file(dst.join("manifest.in"))?; let bulk_dirs: Vec<_> = bulk_dirs .split(',') .filter(|s| !s.is_empty()) .map(Path::new) .collect(); + let mut paths = BTreeSet::new(); copy_with_callback(src, dst, |path, file_type| { // We need paths to be compatible with both Unix and Windows. if path @@ -157,14 +159,20 @@ fn copy_and_manifest(src: &Path, dst: &Path, bulk_dirs: &str) -> Result<()> { if file_type.is_dir() { // Only manifest directories that are explicitly bulk. if bulk_dirs.contains(&path) { - writeln!(&manifest, "dir:{}", string)?; + paths.insert(format!("dir:{}\n", string)); } } else { // Only manifest files that aren't under bulk directories. if !bulk_dirs.iter().any(|d| path.starts_with(d)) { - writeln!(&manifest, "file:{}", string)?; + paths.insert(format!("file:{}\n", string)); } } Ok(()) - }) + })?; + + for path in paths { + manifest.write_all(path.as_bytes())?; + } + + Ok(()) } diff --git a/src/tools/rustdoc-js/tester.js b/src/tools/rustdoc-js/tester.js index 3da4fed33..ea5780f66 100644 --- a/src/tools/rustdoc-js/tester.js +++ b/src/tools/rustdoc-js/tester.js @@ -138,8 +138,20 @@ function valueCheck(fullPath, expected, result, error_text, queryName) { error_text.push('==> Unknown key "' + key + '"'); break; } + let result_v = result[key]; + if (result_v !== null && key === "error") { + result_v.forEach((value, index) => { + value = value.split(" ").join(" "); + if (index % 2 === 1) { + result_v[index] = "`" + value + "`"; + } else { + result_v[index] = value; + } + }); + result_v = result_v.join(""); + } const obj_path = fullPath + (fullPath.length > 0 ? '.' : '') + key; - valueCheck(obj_path, expected[key], result[key], error_text, queryName); + valueCheck(obj_path, expected[key], result_v, error_text, queryName); } } else { expectedValue = JSON.stringify(expected); diff --git a/src/tools/rustfmt/.github/workflows/check_diff.yml b/src/tools/rustfmt/.github/workflows/check_diff.yml new file mode 100644 index 000000000..8bfb58345 --- /dev/null +++ b/src/tools/rustfmt/.github/workflows/check_diff.yml @@ -0,0 +1,33 @@ +name: Diff Check +on: + workflow_dispatch: + inputs: + clone_url: + description: 'Git url of a rustfmt fork to compare against the latest master rustfmt' + required: true + branch_name: + description: 'Name of the feature branch on the forked repo' + required: true + commit_hash: + description: 'Optional commit hash from the feature branch' + required: false + rustfmt_configs: + description: 'Optional comma separated list of rustfmt config options to pass when running the feature branch' + required: false + +jobs: + diff_check: + runs-on: ubuntu-latest + + steps: + - name: checkout + uses: actions/checkout@v3 + + - name: install rustup + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup-init.sh + sh rustup-init.sh -y --default-toolchain none + rustup target add x86_64-unknown-linux-gnu + + - name: check diff + run: bash ${GITHUB_WORKSPACE}/ci/check_diff.sh ${{ github.event.inputs.clone_url }} ${{ github.event.inputs.branch_name }} ${{ github.event.inputs.commit_hash }} ${{ github.event.inputs.rustfmt_configs }} diff --git a/src/tools/rustfmt/.github/workflows/integration.yml b/src/tools/rustfmt/.github/workflows/integration.yml index 4d8899b43..314ce0e84 100644 --- a/src/tools/rustfmt/.github/workflows/integration.yml +++ b/src/tools/rustfmt/.github/workflows/integration.yml @@ -27,7 +27,6 @@ jobs: tempdir, futures-rs, rust-clippy, - failure, ] include: # Allowed Failures @@ -63,9 +62,6 @@ jobs: # Original comment was: temporal build failure due to breaking changes in the nightly compiler - integration: rust-semverver allow-failure: true - # Can be moved back to include section after https://github.com/rust-lang-nursery/failure/pull/298 is merged - - integration: failure - allow-failure: true steps: - name: checkout diff --git a/src/tools/rustfmt/CHANGELOG.md b/src/tools/rustfmt/CHANGELOG.md index 0c1893bf8..60f961fa1 100644 --- a/src/tools/rustfmt/CHANGELOG.md +++ b/src/tools/rustfmt/CHANGELOG.md @@ -2,6 +2,32 @@ ## [Unreleased] +## [1.5.2] 2023-01-24 + +### Fixed + +- Resolve issue when comments are found within const generic defaults in unit structs [#5668](https://github.com/rust-lang/rustfmt/issues/5668) +- Resolve issue when block comments are found within trait generics [#5358](https://github.com/rust-lang/rustfmt/issues/5358) +- Correctly handle alignment of comments containing unicode characters [#5504](https://github.com/rust-lang/rustfmt/issues/5504) +- Properly indent a single generic bound that requires being written across multiple lines [#4689](https://github.com/rust-lang/rustfmt/issues/4689) (n.b. this change is version gated and will only appear when the `version` configuration option is set to `Two`) + +### Changed + +- Renamed `fn_args_layout` configuration option to `fn_params_layout` [#4149](https://github.com/rust-lang/rustfmt/issues/4149). Note that `fn_args_layout` has only been soft deprecated: `fn_args_layout` will continue to work without issue, but rustfmt will display a warning to encourage users to switch to the new name + +### Added + +- New configuration option (`skip_macro_invocations`)[https://rust-lang.github.io/rustfmt/?version=master&search=#skip_macro_invocations] [#5347](https://github.com/rust-lang/rustfmt/pull/5347) that can be used to globally define a single enumerated list of macro calls that rustfmt should skip formatting. rustfmt [currently also supports this via a custom tool attribute](https://github.com/rust-lang/rustfmt#tips), however, these cannot be used in all contexts because [custom inner attributes are unstable](https://github.com/rust-lang/rust/issues/54726) + +### Misc + +- rustfmt now internally supports the ability to have both stable and unstable variants of a configuration option [#5378](https://github.com/rust-lang/rustfmt/issues/5378). This ability will allow the rustfmt team to make certain configuration options available on stable toolchains more quickly because we no longer have to wait for _every_ variant to be stable-ready before stabilizing _any_ variant. + +### Install/Download Options +- **rustup (nightly)** - nightly-2023-01-24 +- **GitHub Release Binaries** - [Release v1.5.2](https://github.com/rust-lang/rustfmt/releases/tag/v1.5.2) +- **Build from source** - [Tag v1.5.2](https://github.com/rust-lang/rustfmt/tree/v1.5.2), see instructions for how to [install rustfmt from source][install-from-source] + ## [1.5.1] 2022-06-24 **N.B** A bug was introduced in v1.5.0/nightly-2022-06-15 which modified formatting. If you happened to run rustfmt over your code with one of those ~10 nightlies it's possible you may have seen formatting changes, and you may see additional changes after this fix since that bug has now been reverted. @@ -840,7 +866,7 @@ from formatting an attribute #3665 - Fix formatting of raw string literals #2983 - Handle chain with try operators with spaces #2986 - Use correct shape in Visual tuple rewriting #2987 -- Impove formatting of arguments with `visual_style = "Visual"` option #2988 +- Improve formatting of arguments with `visual_style = "Visual"` option #2988 - Change `print_diff` to output the correct line number 992b179 - Propagate errors about failing to rewrite a macro 6f318e3 - Handle formatting of long function signature #3010 diff --git a/src/tools/rustfmt/Cargo.lock b/src/tools/rustfmt/Cargo.lock index 311df226d..24166d51c 100644 --- a/src/tools/rustfmt/Cargo.lock +++ b/src/tools/rustfmt/Cargo.lock @@ -476,7 +476,7 @@ checksum = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb" [[package]] name = "rustfmt-config_proc_macro" -version = "0.2.0" +version = "0.3.0" dependencies = [ "proc-macro2", "quote", @@ -485,7 +485,7 @@ dependencies = [ [[package]] name = "rustfmt-nightly" -version = "1.5.1" +version = "1.5.2" dependencies = [ "annotate-snippets", "anyhow", diff --git a/src/tools/rustfmt/Cargo.toml b/src/tools/rustfmt/Cargo.toml index 7a4e02d69..87ce59d02 100644 --- a/src/tools/rustfmt/Cargo.toml +++ b/src/tools/rustfmt/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustfmt-nightly" -version = "1.5.1" +version = "1.5.2" description = "Tool to find and fix Rust formatting issues" repository = "https://github.com/rust-lang/rustfmt" readme = "README.md" @@ -57,7 +57,7 @@ unicode-segmentation = "1.9" unicode-width = "0.1" unicode_categories = "0.1" -rustfmt-config_proc_macro = { version = "0.2", path = "config_proc_macro" } +rustfmt-config_proc_macro = { version = "0.3", path = "config_proc_macro" } # A noop dependency that changes in the Rust repository, it's a bit of a hack. # See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust` diff --git a/src/tools/rustfmt/Configurations.md b/src/tools/rustfmt/Configurations.md index 8b96b9d36..49e7e4e64 100644 --- a/src/tools/rustfmt/Configurations.md +++ b/src/tools/rustfmt/Configurations.md @@ -1,6 +1,6 @@ # Configuring Rustfmt -Rustfmt is designed to be very configurable. You can create a TOML file called `rustfmt.toml` or `.rustfmt.toml`, place it in the project or any other parent directory and it will apply the options in that file. If none of these directories contain such a file, both your home directory and a directory called `rustfmt` in your [global config directory](https://docs.rs/dirs/1.0.4/dirs/fn.config_dir.html) (e.g. `.config/rustfmt/`) are checked as well. +Rustfmt is designed to be very configurable. You can create a TOML file called `rustfmt.toml` or `.rustfmt.toml`, place it in the project or any other parent directory and it will apply the options in that file. If none of these directories contain such a file, both your home directory and a directory called `rustfmt` in your [global config directory](https://docs.rs/dirs/4.0.0/dirs/fn.config_dir.html) (e.g. `.config/rustfmt/`) are checked as well. A possible content of `rustfmt.toml` or `.rustfmt.toml` might look like this: @@ -425,7 +425,7 @@ fn example() { ## `comment_width` -Maximum length of comments. No effect unless`wrap_comments = true`. +Maximum length of comments. No effect unless `wrap_comments = true`. - **Default value**: `80` - **Possible values**: any positive integer @@ -589,7 +589,7 @@ doesn't get ignored when aligning. #### `0` (default): ```rust -enum Bar { +enum Foo { A = 0, Bb = 1, RandomLongVariantGoesHere = 10, @@ -645,7 +645,8 @@ trailing whitespaces. ## `fn_args_layout` -Control the layout of arguments in a function +This option is deprecated and has been renamed to `fn_params_layout` to better communicate that +it affects the layout of parameters in function signatures. - **Default value**: `"Tall"` - **Possible values**: `"Compressed"`, `"Tall"`, `"Vertical"` @@ -753,6 +754,8 @@ trait Lorem { } ``` +See also [`fn_params_layout`](#fn_params_layout) + ## `fn_call_width` Maximum width of the args of a function call before falling back to vertical formatting. @@ -765,6 +768,117 @@ By default this option is set as a percentage of [`max_width`](#max_width) provi See also [`max_width`](#max_width) and [`use_small_heuristics`](#use_small_heuristics) +## `fn_params_layout` + +Control the layout of parameters in function signatures. + +- **Default value**: `"Tall"` +- **Possible values**: `"Compressed"`, `"Tall"`, `"Vertical"` +- **Stable**: Yes + +#### `"Tall"` (default): + +```rust +trait Lorem { + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet); + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet) { + // body + } + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + consectetur: Consectetur, + adipiscing: Adipiscing, + elit: Elit, + ); + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + consectetur: Consectetur, + adipiscing: Adipiscing, + elit: Elit, + ) { + // body + } +} +``` + +#### `"Compressed"`: + +```rust +trait Lorem { + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet); + + fn lorem(ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet) { + // body + } + + fn lorem( + ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, consectetur: Consectetur, + adipiscing: Adipiscing, elit: Elit, + ); + + fn lorem( + ipsum: Ipsum, dolor: Dolor, sit: Sit, amet: Amet, consectetur: Consectetur, + adipiscing: Adipiscing, elit: Elit, + ) { + // body + } +} +``` + +#### `"Vertical"`: + +```rust +trait Lorem { + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + ); + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + ) { + // body + } + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + consectetur: Consectetur, + adipiscing: Adipiscing, + elit: Elit, + ); + + fn lorem( + ipsum: Ipsum, + dolor: Dolor, + sit: Sit, + amet: Amet, + consectetur: Consectetur, + adipiscing: Adipiscing, + elit: Elit, + ) { + // body + } +} +``` + + ## `fn_single_line` Put single-expression functions on a single line @@ -1014,6 +1128,62 @@ macro_rules! foo { See also [`format_macro_matchers`](#format_macro_matchers). +## `skip_macro_invocations` + +Skip formatting the bodies of macro invocations with the following names. + +rustfmt will not format any macro invocation for macros with names set in this list. +Including the special value "*" will prevent any macro invocations from being formatted. + +Note: This option does not have any impact on how rustfmt formats macro definitions. + +- **Default value**: `[]` +- **Possible values**: a list of macro name idents, `["name_0", "name_1", ..., "*"]` +- **Stable**: No (tracking issue: [#5346](https://github.com/rust-lang/rustfmt/issues/5346)) + +#### `[]` (default): + +rustfmt will follow its standard approach to formatting macro invocations. + +No macro invocations will be skipped based on their name. More information about rustfmt's standard macro invocation formatting behavior can be found in [#5437](https://github.com/rust-lang/rustfmt/discussions/5437). + +```rust +lorem!( + const _: u8 = 0; +); + +ipsum!( + const _: u8 = 0; +); +``` + +#### `["lorem"]`: + +The named macro invocations will be skipped. + +```rust +lorem!( + const _: u8 = 0; +); + +ipsum!( + const _: u8 = 0; +); +``` + +#### `["*"]`: + +The special selector `*` will skip all macro invocations. + +```rust +lorem!( + const _: u8 = 0; +); + +ipsum!( + const _: u8 = 0; +); +``` ## `format_strings` @@ -1687,13 +1857,16 @@ pub enum Foo {} ## `imports_granularity` -How imports should be grouped into `use` statements. Imports will be merged or split to the configured level of granularity. +Controls how imports are structured in `use` statements. Imports will be merged or split to the configured level of granularity. + +Similar to other `import` related configuration options, this option operates within the bounds of user-defined groups of imports. See [`group_imports`](#group_imports) for more information on import groups. + +Note that rustfmt will not modify the granularity of imports containing comments if doing so could potentially lose or misplace said comments. - **Default value**: `Preserve` - **Possible values**: `Preserve`, `Crate`, `Module`, `Item`, `One` - **Stable**: No (tracking issue: [#4991](https://github.com/rust-lang/rustfmt/issues/4991)) -Note that rustfmt will not modify the granularity of imports containing comments if doing so could potentially lose or misplace said comments. #### `Preserve` (default): diff --git a/src/tools/rustfmt/Processes.md b/src/tools/rustfmt/Processes.md index f763b5714..61abc87ee 100644 --- a/src/tools/rustfmt/Processes.md +++ b/src/tools/rustfmt/Processes.md @@ -2,7 +2,7 @@ This document outlines processes regarding management of rustfmt. # Stabilising an Option -In this Section, we describe how to stabilise an option of the rustfmt's configration. +In this Section, we describe how to stabilise an option of the rustfmt's configuration. ## Conditions diff --git a/src/tools/rustfmt/ci/build_and_test.bat b/src/tools/rustfmt/ci/build_and_test.bat index ef4101778..69dae1fff 100755 --- a/src/tools/rustfmt/ci/build_and_test.bat +++ b/src/tools/rustfmt/ci/build_and_test.bat @@ -1,4 +1,5 @@ set "RUSTFLAGS=-D warnings" +set "RUSTFMT_CI=1" :: Print version information rustc -Vv || exit /b 1 diff --git a/src/tools/rustfmt/ci/build_and_test.sh b/src/tools/rustfmt/ci/build_and_test.sh index 8fa0f67b0..949918532 100755 --- a/src/tools/rustfmt/ci/build_and_test.sh +++ b/src/tools/rustfmt/ci/build_and_test.sh @@ -3,6 +3,7 @@ set -euo pipefail export RUSTFLAGS="-D warnings" +export RUSTFMT_CI=1 # Print version information rustc -Vv diff --git a/src/tools/rustfmt/ci/check_diff.sh b/src/tools/rustfmt/ci/check_diff.sh new file mode 100755 index 000000000..062c2dd86 --- /dev/null +++ b/src/tools/rustfmt/ci/check_diff.sh @@ -0,0 +1,199 @@ +#!/bin/bash + +function print_usage() { + echo "usage check_diff REMOTE_REPO FEATURE_BRANCH [COMMIT_HASH] [OPTIONAL_RUSTFMT_CONFIGS]" +} + +if [ $# -le 1 ]; then + print_usage + exit 1 +fi + +REMOTE_REPO=$1 +FEATURE_BRANCH=$2 +OPTIONAL_COMMIT_HASH=$3 +OPTIONAL_RUSTFMT_CONFIGS=$4 + +# OUTPUT array used to collect all the status of running diffs on various repos +STATUSES=() + +# Clone a git repository and cd into it. +# +# Parameters: +# $1: git clone url +# $2: directory where the repo should be cloned +function clone_repo() { + GIT_TERMINAL_PROMPT=0 git clone --quiet $1 --depth 1 $2 && cd $2 +} + +# Initialize Git submoduels for the repo. +# +# Parameters +# $1: list of directories to initialize +function init_submodules() { + git submodule update --init $1 +} + +# Run rusfmt with the --check flag to see if a diff is produced. +# +# Parameters: +# $1: Path to a rustfmt binary +# $2: Output file path for the diff +# $3: Any additional configuration options to pass to rustfmt +# +# Globlas: +# $OPTIONAL_RUSTFMT_CONFIGS: Optional configs passed to the script from $4 +function create_diff() { + local config; + if [ -z "$3" ]; then + config="--config=error_on_line_overflow=false,error_on_unformatted=false" + else + config="--config=error_on_line_overflow=false,error_on_unformatted=false,$OPTIONAL_RUSTFMT_CONFIGS" + fi + + for i in `find . | grep "\.rs$"` + do + $1 --unstable-features --skip-children --check --color=always $config $i >> $2 2>/dev/null + done +} + +# Run the master rustfmt binary and the feature branch binary in the current directory and compare the diffs +# +# Parameters +# $1: Name of the repository (used for logging) +# +# Globlas: +# $RUSFMT_BIN: Path to the rustfmt master binary. Created when running `compile_rustfmt` +# $FEATURE_BIN: Path to the rustfmt feature binary. Created when running `compile_rustfmt` +# $OPTIONAL_RUSTFMT_CONFIGS: Optional configs passed to the script from $4 +function check_diff() { + echo "running rustfmt (master) on $1" + create_diff $RUSFMT_BIN rustfmt_diff.txt + + echo "running rustfmt (feature) on $1" + create_diff $FEATURE_BIN feature_diff.txt $OPTIONAL_RUSTFMT_CONFIGS + + echo "checking diff" + local diff; + # we don't add color to the diff since we added color when running rustfmt --check. + # tail -n + 6 removes the git diff header info + # cut -c 2- removes the leading diff characters("+","-"," ") from running git diff. + # Again, the diff output we care about was already added when we ran rustfmt --check + diff=$( + git --no-pager diff --color=never \ + --unified=0 --no-index rustfmt_diff.txt feature_diff.txt 2>&1 | tail -n +6 | cut -c 2- + ) + + if [ -z "$diff" ]; then + echo "no diff detected between rustfmt and the feture branch" + return 0 + else + echo "$diff" + return 1 + fi +} + +# Compiles and produces two rustfmt binaries. +# One for the current master, and another for the feature branch +# +# Parameters: +# $1: Directory where rustfmt will be cloned +# +# Globlas: +# $REMOTE_REPO: Clone URL to the rustfmt fork that we want to test +# $FEATURE_BRANCH: Name of the feature branch +# $OPTIONAL_COMMIT_HASH: Optional commit hash that will be checked out if provided +function compile_rustfmt() { + RUSTFMT_REPO="https://github.com/rust-lang/rustfmt.git" + clone_repo $RUSTFMT_REPO $1 + git remote add feature $REMOTE_REPO + git fetch feature $FEATURE_BRANCH + + cargo build --release --bin rustfmt && cp target/release/rustfmt $1/rustfmt + if [ -z "$OPTIONAL_COMMIT_HASH" ]; then + git switch $FEATURE_BRANCH + else + git switch $OPTIONAL_COMMIT_HASH --detach + fi + cargo build --release --bin rustfmt && cp target/release/rustfmt $1/feature_rustfmt + RUSFMT_BIN=$1/rustfmt + FEATURE_BIN=$1/feature_rustfmt +} + +# Check the diff for running rustfmt and the feature branch on all the .rs files in the repo. +# +# Parameters +# $1: Clone URL for the repo +# $2: Name of the repo (mostly used for logging) +# $3: Path to any submodules that should be initialized +function check_repo() { + WORKDIR=$(pwd) + REPO_URL=$1 + REPO_NAME=$2 + SUBMODULES=$3 + + local tmp_dir; + tmp_dir=$(mktemp -d -t $REPO_NAME-XXXXXXXX) + clone_repo $REPO_URL $tmp_dir + + if [ ! -z "$SUBMODULES" ]; then + init_submodules $SUBMODULES + fi + + check_diff $REPO_NAME + # append the status of running `check_diff` to the STATUSES array + STATUSES+=($?) + + echo "removing tmp_dir $tmp_dir" + rm -rf $tmp_dir + cd $WORKDIR +} + +function main() { + tmp_dir=$(mktemp -d -t rustfmt-XXXXXXXX) + echo Created tmp_dir $tmp_dir + + compile_rustfmt $tmp_dir + + # run checks + check_repo "https://github.com/rust-lang/rust.git" rust-lang-rust + check_repo "https://github.com/rust-lang/cargo.git" cargo + check_repo "https://github.com/rust-lang/miri.git" miri + check_repo "https://github.com/rust-lang/rust-analyzer.git" rust-analyzer + check_repo "https://github.com/bitflags/bitflags.git" bitflags + check_repo "https://github.com/rust-lang/log.git" log + check_repo "https://github.com/rust-lang/mdBook.git" mdBook + check_repo "https://github.com/rust-lang/packed_simd.git" packed_simd + check_repo "https://github.com/rust-lang/rust-semverver.git" check_repo + check_repo "https://github.com/Stebalien/tempfile.git" tempfile + check_repo "https://github.com/rust-lang/futures-rs.git" futures-rs + check_repo "https://github.com/dtolnay/anyhow.git" anyhow + check_repo "https://github.com/dtolnay/thiserror.git" thiserror + check_repo "https://github.com/dtolnay/syn.git" syn + check_repo "https://github.com/serde-rs/serde.git" serde + check_repo "https://github.com/rust-lang/rustlings.git" rustlings + check_repo "https://github.com/rust-lang/rustup.git" rustup + check_repo "https://github.com/SergioBenitez/Rocket.git" Rocket + check_repo "https://github.com/rustls/rustls.git" rustls + check_repo "https://github.com/rust-lang/rust-bindgen.git" rust-bindgen + check_repo "https://github.com/hyperium/hyper.git" hyper + check_repo "https://github.com/actix/actix.git" actix + check_repo "https://github.com/denoland/deno.git" denoland_deno + + # cleanup temp dir + echo removing tmp_dir $tmp_dir + rm -rf $tmp_dir + + # figure out the exit code + for status in ${STATUSES[@]} + do + if [ $status -eq 1 ]; then + echo "formatting diff found 💔" + return 1 + fi + done + + echo "no diff found 😊" +} + +main diff --git a/src/tools/rustfmt/ci/integration.sh b/src/tools/rustfmt/ci/integration.sh index 562d5d70c..19d502bc5 100755 --- a/src/tools/rustfmt/ci/integration.sh +++ b/src/tools/rustfmt/ci/integration.sh @@ -91,14 +91,28 @@ case ${INTEGRATION} in cd - ;; crater) - git clone --depth=1 https://github.com/rust-lang-nursery/${INTEGRATION}.git + git clone --depth=1 https://github.com/rust-lang/${INTEGRATION}.git cd ${INTEGRATION} show_head check_fmt_with_lib_tests cd - ;; + bitflags) + git clone --depth=1 https://github.com/bitflags/${INTEGRATION}.git + cd ${INTEGRATION} + show_head + check_fmt_with_all_tests + cd - + ;; + error-chain | tempdir) + git clone --depth=1 https://github.com/rust-lang-deprecated/${INTEGRATION}.git + cd ${INTEGRATION} + show_head + check_fmt_with_all_tests + cd - + ;; *) - git clone --depth=1 https://github.com/rust-lang-nursery/${INTEGRATION}.git + git clone --depth=1 https://github.com/rust-lang/${INTEGRATION}.git cd ${INTEGRATION} show_head check_fmt_with_all_tests diff --git a/src/tools/rustfmt/config_proc_macro/Cargo.lock b/src/tools/rustfmt/config_proc_macro/Cargo.lock index ecf561f28..49f2f72a8 100644 --- a/src/tools/rustfmt/config_proc_macro/Cargo.lock +++ b/src/tools/rustfmt/config_proc_macro/Cargo.lock @@ -22,7 +22,7 @@ dependencies = [ [[package]] name = "rustfmt-config_proc_macro" -version = "0.2.0" +version = "0.3.0" dependencies = [ "proc-macro2", "quote", diff --git a/src/tools/rustfmt/config_proc_macro/Cargo.toml b/src/tools/rustfmt/config_proc_macro/Cargo.toml index a41b3a5e6..d10d0469c 100644 --- a/src/tools/rustfmt/config_proc_macro/Cargo.toml +++ b/src/tools/rustfmt/config_proc_macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustfmt-config_proc_macro" -version = "0.2.0" +version = "0.3.0" edition = "2018" description = "A collection of procedural macros for rustfmt" license = "Apache-2.0/MIT" diff --git a/src/tools/rustfmt/config_proc_macro/src/attrs.rs b/src/tools/rustfmt/config_proc_macro/src/attrs.rs index 0baba046f..dd18ff572 100644 --- a/src/tools/rustfmt/config_proc_macro/src/attrs.rs +++ b/src/tools/rustfmt/config_proc_macro/src/attrs.rs @@ -1,8 +1,10 @@ //! This module provides utilities for handling attributes on variants -//! of `config_type` enum. Currently there are two types of attributes -//! that could appear on the variants of `config_type` enum: `doc_hint` -//! and `value`. Both comes in the form of name-value pair whose value -//! is string literal. +//! of `config_type` enum. Currently there are the following attributes +//! that could appear on the variants of `config_type` enum: +//! +//! - `doc_hint`: name-value pair whose value is string literal +//! - `value`: name-value pair whose value is string literal +//! - `unstable_variant`: name only /// Returns the value of the first `doc_hint` attribute in the given slice or /// `None` if `doc_hint` attribute is not available. @@ -27,6 +29,11 @@ pub fn find_config_value(attrs: &[syn::Attribute]) -> Option<String> { attrs.iter().filter_map(config_value).next() } +/// Returns `true` if the there is at least one `unstable` attribute in the given slice. +pub fn any_unstable_variant(attrs: &[syn::Attribute]) -> bool { + attrs.iter().any(is_unstable_variant) +} + /// Returns a string literal value if the given attribute is `value` /// attribute or `None` otherwise. pub fn config_value(attr: &syn::Attribute) -> Option<String> { @@ -38,6 +45,11 @@ pub fn is_config_value(attr: &syn::Attribute) -> bool { is_attr_name_value(attr, "value") } +/// Returns `true` if the given attribute is an `unstable` attribute. +pub fn is_unstable_variant(attr: &syn::Attribute) -> bool { + is_attr_path(attr, "unstable_variant") +} + fn is_attr_name_value(attr: &syn::Attribute, name: &str) -> bool { attr.parse_meta().ok().map_or(false, |meta| match meta { syn::Meta::NameValue(syn::MetaNameValue { ref path, .. }) if path.is_ident(name) => true, @@ -45,6 +57,13 @@ fn is_attr_name_value(attr: &syn::Attribute, name: &str) -> bool { }) } +fn is_attr_path(attr: &syn::Attribute, name: &str) -> bool { + attr.parse_meta().ok().map_or(false, |meta| match meta { + syn::Meta::Path(path) if path.is_ident(name) => true, + _ => false, + }) +} + fn get_name_value_str_lit(attr: &syn::Attribute, name: &str) -> Option<String> { attr.parse_meta().ok().and_then(|meta| match meta { syn::Meta::NameValue(syn::MetaNameValue { diff --git a/src/tools/rustfmt/config_proc_macro/src/item_enum.rs b/src/tools/rustfmt/config_proc_macro/src/item_enum.rs index dcee77a85..731a7ea06 100644 --- a/src/tools/rustfmt/config_proc_macro/src/item_enum.rs +++ b/src/tools/rustfmt/config_proc_macro/src/item_enum.rs @@ -1,5 +1,6 @@ use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, quote_spanned}; +use syn::spanned::Spanned; use crate::attrs::*; use crate::utils::*; @@ -47,12 +48,23 @@ fn process_variant(variant: &syn::Variant) -> TokenStream { let metas = variant .attrs .iter() - .filter(|attr| !is_doc_hint(attr) && !is_config_value(attr)); + .filter(|attr| !is_doc_hint(attr) && !is_config_value(attr) && !is_unstable_variant(attr)); let attrs = fold_quote(metas, |meta| quote!(#meta)); let syn::Variant { ident, fields, .. } = variant; quote!(#attrs #ident #fields) } +/// Return the correct syntax to pattern match on the enum variant, discarding all +/// internal field data. +fn fields_in_variant(variant: &syn::Variant) -> TokenStream { + // With thanks to https://stackoverflow.com/a/65182902 + match &variant.fields { + syn::Fields::Unnamed(_) => quote_spanned! { variant.span() => (..) }, + syn::Fields::Unit => quote_spanned! { variant.span() => }, + syn::Fields::Named(_) => quote_spanned! { variant.span() => {..} }, + } +} + fn impl_doc_hint(ident: &syn::Ident, variants: &Variants) -> TokenStream { let doc_hint = variants .iter() @@ -60,12 +72,26 @@ fn impl_doc_hint(ident: &syn::Ident, variants: &Variants) -> TokenStream { .collect::<Vec<_>>() .join("|"); let doc_hint = format!("[{}]", doc_hint); + + let variant_stables = variants + .iter() + .map(|v| (&v.ident, fields_in_variant(&v), !unstable_of_variant(v))); + let match_patterns = fold_quote(variant_stables, |(v, fields, stable)| { + quote! { + #ident::#v #fields => #stable, + } + }); quote! { use crate::config::ConfigType; impl ConfigType for #ident { fn doc_hint() -> String { #doc_hint.to_owned() } + fn stable_variant(&self) -> bool { + match self { + #match_patterns + } + } } } } @@ -123,13 +149,21 @@ fn impl_from_str(ident: &syn::Ident, variants: &Variants) -> TokenStream { } fn doc_hint_of_variant(variant: &syn::Variant) -> String { - find_doc_hint(&variant.attrs).unwrap_or(variant.ident.to_string()) + let mut text = find_doc_hint(&variant.attrs).unwrap_or(variant.ident.to_string()); + if unstable_of_variant(&variant) { + text.push_str(" (unstable)") + }; + text } fn config_value_of_variant(variant: &syn::Variant) -> String { find_config_value(&variant.attrs).unwrap_or(variant.ident.to_string()) } +fn unstable_of_variant(variant: &syn::Variant) -> bool { + any_unstable_variant(&variant.attrs) +} + fn impl_serde(ident: &syn::Ident, variants: &Variants) -> TokenStream { let arms = fold_quote(variants.iter(), |v| { let v_ident = &v.ident; diff --git a/src/tools/rustfmt/config_proc_macro/src/lib.rs b/src/tools/rustfmt/config_proc_macro/src/lib.rs index e772c53f4..0c54c132c 100644 --- a/src/tools/rustfmt/config_proc_macro/src/lib.rs +++ b/src/tools/rustfmt/config_proc_macro/src/lib.rs @@ -69,3 +69,16 @@ pub fn stable_only_test(_args: TokenStream, input: TokenStream) -> TokenStream { TokenStream::from_str("").unwrap() } } + +/// Used to conditionally output the TokenStream for tests that should be run as part of rustfmts +/// test suite, but should be ignored when running in the rust-lang/rust test suite. +#[proc_macro_attribute] +pub fn rustfmt_only_ci_test(_args: TokenStream, input: TokenStream) -> TokenStream { + if option_env!("RUSTFMT_CI").is_some() { + input + } else { + let mut token_stream = TokenStream::from_str("#[ignore]").unwrap(); + token_stream.extend(input); + token_stream + } +} diff --git a/src/tools/rustfmt/config_proc_macro/tests/smoke.rs b/src/tools/rustfmt/config_proc_macro/tests/smoke.rs index 940a8a0c2..c8a83e39c 100644 --- a/src/tools/rustfmt/config_proc_macro/tests/smoke.rs +++ b/src/tools/rustfmt/config_proc_macro/tests/smoke.rs @@ -1,6 +1,7 @@ pub mod config { pub trait ConfigType: Sized { fn doc_hint() -> String; + fn stable_variant(&self) -> bool; } } diff --git a/src/tools/rustfmt/rust-toolchain b/src/tools/rustfmt/rust-toolchain index 2640a9e0e..22283b3d6 100644 --- a/src/tools/rustfmt/rust-toolchain +++ b/src/tools/rustfmt/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-06-21" -components = ["rustc-dev"] +channel = "nightly-2023-01-24" +components = ["llvm-tools", "rustc-dev"] diff --git a/src/tools/rustfmt/src/attr.rs b/src/tools/rustfmt/src/attr.rs index c503eeeb9..5648e1254 100644 --- a/src/tools/rustfmt/src/attr.rs +++ b/src/tools/rustfmt/src/attr.rs @@ -336,7 +336,7 @@ impl Rewrite for ast::Attribute { } else { let should_skip = self .ident() - .map(|s| context.skip_context.skip_attribute(s.name.as_str())) + .map(|s| context.skip_context.attributes.skip(s.name.as_str())) .unwrap_or(false); let prefix = attr_prefix(self); @@ -390,7 +390,7 @@ impl Rewrite for [ast::Attribute] { // Determine if the source text is annotated with `#[rustfmt::skip::attributes(derive)]` // or `#![rustfmt::skip::attributes(derive)]` - let skip_derives = context.skip_context.skip_attribute("derive"); + let skip_derives = context.skip_context.attributes.skip("derive"); // This is not just a simple map because we need to handle doc comments // (where we take as many doc comment attributes as possible) and possibly diff --git a/src/tools/rustfmt/src/bin/main.rs b/src/tools/rustfmt/src/bin/main.rs index 8e871e61f..be64559e8 100644 --- a/src/tools/rustfmt/src/bin/main.rs +++ b/src/tools/rustfmt/src/bin/main.rs @@ -136,7 +136,7 @@ fn make_opts() -> Options { "l", "files-with-diff", "Prints the names of mismatched files that were formatted. Prints the names of \ - files that would be formated when used with `--check` mode. ", + files that would be formatted when used with `--check` mode. ", ); opts.optmulti( "", diff --git a/src/tools/rustfmt/src/cargo-fmt/main.rs b/src/tools/rustfmt/src/cargo-fmt/main.rs index 9031d29b4..2b714b68d 100644 --- a/src/tools/rustfmt/src/cargo-fmt/main.rs +++ b/src/tools/rustfmt/src/cargo-fmt/main.rs @@ -198,12 +198,10 @@ fn convert_message_format_to_rustfmt_args( Ok(()) } "human" => Ok(()), - _ => { - return Err(format!( - "invalid --message-format value: {}. Allowed values are: short|json|human", - message_format - )); - } + _ => Err(format!( + "invalid --message-format value: {}. Allowed values are: short|json|human", + message_format + )), } } @@ -215,7 +213,7 @@ fn print_usage_to_stderr(reason: &str) { .expect("failed to write to stderr"); } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Verbosity { Verbose, Normal, diff --git a/src/tools/rustfmt/src/cargo-fmt/test/mod.rs b/src/tools/rustfmt/src/cargo-fmt/test/mod.rs index 56e52fbab..696326e4f 100644 --- a/src/tools/rustfmt/src/cargo-fmt/test/mod.rs +++ b/src/tools/rustfmt/src/cargo-fmt/test/mod.rs @@ -70,9 +70,9 @@ fn mandatory_separator() { .is_err() ); assert!( - !Opts::command() + Opts::command() .try_get_matches_from(&["test", "--", "--emit"]) - .is_err() + .is_ok() ); } diff --git a/src/tools/rustfmt/src/chains.rs b/src/tools/rustfmt/src/chains.rs index a1a73cf4b..cbe523c6c 100644 --- a/src/tools/rustfmt/src/chains.rs +++ b/src/tools/rustfmt/src/chains.rs @@ -70,10 +70,66 @@ use crate::rewrite::{Rewrite, RewriteContext}; use crate::shape::Shape; use crate::source_map::SpanUtils; use crate::utils::{ - self, first_line_width, last_line_extendable, last_line_width, mk_sp, rewrite_ident, - trimmed_last_line_width, wrap_str, + self, filtered_str_fits, first_line_width, last_line_extendable, last_line_width, mk_sp, + rewrite_ident, trimmed_last_line_width, wrap_str, }; +use thin_vec::ThinVec; + +/// Provides the original input contents from the span +/// of a chain element with trailing spaces trimmed. +fn format_overflow_style(span: Span, context: &RewriteContext<'_>) -> Option<String> { + context.snippet_provider.span_to_snippet(span).map(|s| { + s.lines() + .map(|l| l.trim_end()) + .collect::<Vec<_>>() + .join("\n") + }) +} + +fn format_chain_item( + item: &ChainItem, + context: &RewriteContext<'_>, + rewrite_shape: Shape, + allow_overflow: bool, +) -> Option<String> { + if allow_overflow { + item.rewrite(context, rewrite_shape) + .or_else(|| format_overflow_style(item.span, context)) + } else { + item.rewrite(context, rewrite_shape) + } +} + +fn get_block_child_shape( + prev_ends_with_block: bool, + context: &RewriteContext<'_>, + shape: Shape, +) -> Shape { + if prev_ends_with_block { + shape.block_indent(0) + } else { + shape.block_indent(context.config.tab_spaces()) + } + .with_max_width(context.config) +} + +fn get_visual_style_child_shape( + context: &RewriteContext<'_>, + shape: Shape, + offset: usize, + parent_overflowing: bool, +) -> Option<Shape> { + if !parent_overflowing { + shape + .with_max_width(context.config) + .offset_left(offset) + .map(|s| s.visual_indent(0)) + } else { + Some(shape.visual_indent(offset)) + } +} + pub(crate) fn rewrite_chain( expr: &ast::Expr, context: &RewriteContext<'_>, @@ -114,7 +170,7 @@ enum ChainItemKind { MethodCall( ast::PathSegment, Vec<ast::GenericArg>, - Vec<ptr::P<ast::Expr>>, + ThinVec<ptr::P<ast::Expr>>, ), StructField(symbol::Ident), TupleField(symbol::Ident, bool), @@ -496,6 +552,8 @@ struct ChainFormatterShared<'a> { // The number of children in the chain. This is not equal to `self.children.len()` // because `self.children` will change size as we process the chain. child_count: usize, + // Whether elements are allowed to overflow past the max_width limit + allow_overflow: bool, } impl<'a> ChainFormatterShared<'a> { @@ -505,6 +563,8 @@ impl<'a> ChainFormatterShared<'a> { rewrites: Vec::with_capacity(chain.children.len() + 1), fits_single_line: false, child_count: chain.children.len(), + // TODO(calebcartwright) + allow_overflow: false, } } @@ -517,6 +577,14 @@ impl<'a> ChainFormatterShared<'a> { } } + fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> { + for item in &self.children[..self.children.len() - 1] { + let rewrite = format_chain_item(item, context, child_shape, self.allow_overflow)?; + self.rewrites.push(rewrite); + } + Some(()) + } + // Rewrite the last child. The last child of a chain requires special treatment. We need to // know whether 'overflowing' the last child make a better formatting: // @@ -729,22 +797,12 @@ impl<'a> ChainFormatter for ChainFormatterBlock<'a> { } fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<Shape> { - Some( - if self.root_ends_with_block { - shape.block_indent(0) - } else { - shape.block_indent(context.config.tab_spaces()) - } - .with_max_width(context.config), - ) + let block_end = self.root_ends_with_block; + Some(get_block_child_shape(block_end, context, shape)) } fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> { - for item in &self.shared.children[..self.shared.children.len() - 1] { - let rewrite = item.rewrite(context, child_shape)?; - self.shared.rewrites.push(rewrite); - } - Some(()) + self.shared.format_children(context, child_shape) } fn format_last_child( @@ -808,15 +866,14 @@ impl<'a> ChainFormatter for ChainFormatterVisual<'a> { .visual_indent(self.offset) .sub_width(self.offset)?; let rewrite = item.rewrite(context, child_shape)?; - match wrap_str(rewrite, context.config.max_width(), shape) { - Some(rewrite) => root_rewrite.push_str(&rewrite), - None => { - // We couldn't fit in at the visual indent, try the last - // indent. - let rewrite = item.rewrite(context, parent_shape)?; - root_rewrite.push_str(&rewrite); - self.offset = 0; - } + if filtered_str_fits(&rewrite, context.config.max_width(), shape) { + root_rewrite.push_str(&rewrite); + } else { + // We couldn't fit in at the visual indent, try the last + // indent. + let rewrite = item.rewrite(context, parent_shape)?; + root_rewrite.push_str(&rewrite); + self.offset = 0; } self.shared.children = &self.shared.children[1..]; @@ -827,18 +884,17 @@ impl<'a> ChainFormatter for ChainFormatterVisual<'a> { } fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<Shape> { - shape - .with_max_width(context.config) - .offset_left(self.offset) - .map(|s| s.visual_indent(0)) + get_visual_style_child_shape( + context, + shape, + self.offset, + // TODO(calebcartwright): self.shared.permissibly_overflowing_parent, + false, + ) } fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> { - for item in &self.shared.children[..self.shared.children.len() - 1] { - let rewrite = item.rewrite(context, child_shape)?; - self.shared.rewrites.push(rewrite); - } - Some(()) + self.shared.format_children(context, child_shape) } fn format_last_child( diff --git a/src/tools/rustfmt/src/closures.rs b/src/tools/rustfmt/src/closures.rs index 8fd0fcf8f..340113866 100644 --- a/src/tools/rustfmt/src/closures.rs +++ b/src/tools/rustfmt/src/closures.rs @@ -1,5 +1,6 @@ use rustc_ast::{ast, ptr}; use rustc_span::Span; +use thin_vec::thin_vec; use crate::attr::get_attrs_from_stmt; use crate::config::lists::*; @@ -150,7 +151,7 @@ fn rewrite_closure_with_block( } let block = ast::Block { - stmts: vec![ast::Stmt { + stmts: thin_vec![ast::Stmt { id: ast::NodeId::root(), kind: ast::StmtKind::Expr(ptr::P(body.clone())), span: body.span, diff --git a/src/tools/rustfmt/src/config/config_type.rs b/src/tools/rustfmt/src/config/config_type.rs index c5e61658a..54ca7676d 100644 --- a/src/tools/rustfmt/src/config/config_type.rs +++ b/src/tools/rustfmt/src/config/config_type.rs @@ -1,4 +1,5 @@ use crate::config::file_lines::FileLines; +use crate::config::macro_names::MacroSelectors; use crate::config::options::{IgnoreList, WidthHeuristics}; /// Trait for types that can be used in `Config`. @@ -6,6 +7,14 @@ pub(crate) trait ConfigType: Sized { /// Returns hint text for use in `Config::print_docs()`. For enum types, this is a /// pipe-separated list of variants; for other types it returns `<type>`. fn doc_hint() -> String; + + /// Return `true` if the variant (i.e. value of this type) is stable. + /// + /// By default, return true for all values. Enums annotated with `#[config_type]` + /// are automatically implemented, based on the `#[unstable_variant]` annotation. + fn stable_variant(&self) -> bool { + true + } } impl ConfigType for bool { @@ -38,6 +47,12 @@ impl ConfigType for FileLines { } } +impl ConfigType for MacroSelectors { + fn doc_hint() -> String { + String::from("[<string>, ...]") + } +} + impl ConfigType for WidthHeuristics { fn doc_hint() -> String { String::new() @@ -51,6 +66,13 @@ impl ConfigType for IgnoreList { } macro_rules! create_config { + // Options passed in to the macro. + // + // - $i: the ident name of the option + // - $ty: the type of the option value + // - $def: the default value of the option + // - $stb: true if the option is stable + // - $dstring: description of the option ($($i:ident: $ty:ty, $def:expr, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => ( #[cfg(test)] use std::collections::HashSet; @@ -61,9 +83,12 @@ macro_rules! create_config { #[derive(Clone)] #[allow(unreachable_pub)] pub struct Config { - // For each config item, we store a bool indicating whether it has - // been accessed and the value, and a bool whether the option was - // manually initialised, or taken from the default, + // For each config item, we store: + // + // - 0: true if the value has been access + // - 1: true if the option was manually initialized + // - 2: the option value + // - 3: true if the option is unstable $($i: (Cell<bool>, bool, $ty, bool)),+ } @@ -102,6 +127,7 @@ macro_rules! create_config { | "array_width" | "chain_width" => self.0.set_heuristics(), "merge_imports" => self.0.set_merge_imports(), + "fn_args_layout" => self.0.set_fn_args_layout(), &_ => (), } } @@ -143,24 +169,20 @@ macro_rules! create_config { fn fill_from_parsed_config(mut self, parsed: PartialConfig, dir: &Path) -> Config { $( - if let Some(val) = parsed.$i { - if self.$i.3 { + if let Some(option_value) = parsed.$i { + let option_stable = self.$i.3; + if $crate::config::config_type::is_stable_option_and_value( + stringify!($i), option_stable, &option_value + ) { self.$i.1 = true; - self.$i.2 = val; - } else { - if crate::is_nightly_channel!() { - self.$i.1 = true; - self.$i.2 = val; - } else { - eprintln!("Warning: can't set `{} = {:?}`, unstable features are only \ - available in nightly channel.", stringify!($i), val); - } + self.$i.2 = option_value; } } )+ self.set_heuristics(); self.set_ignore(dir); self.set_merge_imports(); + self.set_fn_args_layout(); self } @@ -221,12 +243,22 @@ macro_rules! create_config { match key { $( stringify!($i) => { - self.$i.1 = true; - self.$i.2 = val.parse::<$ty>() + let option_value = val.parse::<$ty>() .expect(&format!("Failed to parse override for {} (\"{}\") as a {}", stringify!($i), val, stringify!($ty))); + + // Users are currently allowed to set unstable + // options/variants via the `--config` options override. + // + // There is ongoing discussion about how to move forward here: + // https://github.com/rust-lang/rustfmt/pull/5379 + // + // For now, do not validate whether the option or value is stable, + // just always set it. + self.$i.1 = true; + self.$i.2 = option_value; } )+ _ => panic!("Unknown config key in override: {}", key) @@ -243,14 +275,21 @@ macro_rules! create_config { | "array_width" | "chain_width" => self.set_heuristics(), "merge_imports" => self.set_merge_imports(), + "fn_args_layout" => self.set_fn_args_layout(), &_ => (), } } #[allow(unreachable_pub)] pub fn is_hidden_option(name: &str) -> bool { - const HIDE_OPTIONS: [&str; 5] = - ["verbose", "verbose_diff", "file_lines", "width_heuristics", "merge_imports"]; + const HIDE_OPTIONS: [&str; 6] = [ + "verbose", + "verbose_diff", + "file_lines", + "width_heuristics", + "merge_imports", + "fn_args_layout" + ]; HIDE_OPTIONS.contains(&name) } @@ -400,6 +439,18 @@ macro_rules! create_config { } } + fn set_fn_args_layout(&mut self) { + if self.was_set().fn_args_layout() { + eprintln!( + "Warning: the `fn_args_layout` option is deprecated. \ + Use `fn_params_layout`. instead" + ); + if !self.was_set().fn_params_layout() { + self.fn_params_layout.2 = self.fn_args_layout(); + } + } + } + #[allow(unreachable_pub)] /// Returns `true` if the config key was explicitly set and is the default value. pub fn is_default(&self, key: &str) -> bool { @@ -424,3 +475,38 @@ macro_rules! create_config { } ) } + +pub(crate) fn is_stable_option_and_value<T>( + option_name: &str, + option_stable: bool, + option_value: &T, +) -> bool +where + T: PartialEq + std::fmt::Debug + ConfigType, +{ + let nightly = crate::is_nightly_channel!(); + let variant_stable = option_value.stable_variant(); + match (nightly, option_stable, variant_stable) { + // Stable with an unstable option + (false, false, _) => { + eprintln!( + "Warning: can't set `{} = {:?}`, unstable features are only \ + available in nightly channel.", + option_name, option_value + ); + false + } + // Stable with a stable option, but an unstable variant + (false, true, false) => { + eprintln!( + "Warning: can't set `{} = {:?}`, unstable variants are only \ + available in nightly channel.", + option_name, option_value + ); + false + } + // Nightly: everything allowed + // Stable with stable option and variant: allowed + (true, _, _) | (false, true, true) => true, + } +} diff --git a/src/tools/rustfmt/src/config/macro_names.rs b/src/tools/rustfmt/src/config/macro_names.rs new file mode 100644 index 000000000..26ad78d6d --- /dev/null +++ b/src/tools/rustfmt/src/config/macro_names.rs @@ -0,0 +1,118 @@ +//! This module contains types and functions to support formatting specific macros. + +use itertools::Itertools; +use std::{fmt, str}; + +use serde::{Deserialize, Serialize}; +use serde_json as json; +use thiserror::Error; + +/// Defines the name of a macro. +#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)] +pub struct MacroName(String); + +impl MacroName { + pub fn new(other: String) -> Self { + Self(other) + } +} + +impl fmt::Display for MacroName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl From<MacroName> for String { + fn from(other: MacroName) -> Self { + other.0 + } +} + +/// Defines a selector to match against a macro. +#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)] +pub enum MacroSelector { + Name(MacroName), + All, +} + +impl fmt::Display for MacroSelector { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Name(name) => name.fmt(f), + Self::All => write!(f, "*"), + } + } +} + +impl str::FromStr for MacroSelector { + type Err = std::convert::Infallible; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(match s { + "*" => MacroSelector::All, + name => MacroSelector::Name(MacroName(name.to_owned())), + }) + } +} + +/// A set of macro selectors. +#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] +pub struct MacroSelectors(pub Vec<MacroSelector>); + +impl fmt::Display for MacroSelectors { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.iter().format(", ")) + } +} + +#[derive(Error, Debug)] +pub enum MacroSelectorsError { + #[error("{0}")] + Json(json::Error), +} + +// This impl is needed for `Config::override_value` to work for use in tests. +impl str::FromStr for MacroSelectors { + type Err = MacroSelectorsError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let raw: Vec<&str> = json::from_str(s).map_err(MacroSelectorsError::Json)?; + Ok(Self( + raw.into_iter() + .map(|raw| { + MacroSelector::from_str(raw).expect("MacroSelector from_str is infallible") + }) + .collect(), + )) + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::str::FromStr; + + #[test] + fn macro_names_from_str() { + let macro_names = MacroSelectors::from_str(r#"["foo", "*", "bar"]"#).unwrap(); + assert_eq!( + macro_names, + MacroSelectors( + [ + MacroSelector::Name(MacroName("foo".to_owned())), + MacroSelector::All, + MacroSelector::Name(MacroName("bar".to_owned())) + ] + .into_iter() + .collect() + ) + ); + } + + #[test] + fn macro_names_display() { + let macro_names = MacroSelectors::from_str(r#"["foo", "*", "bar"]"#).unwrap(); + assert_eq!(format!("{}", macro_names), "foo, *, bar"); + } +} diff --git a/src/tools/rustfmt/src/config/mod.rs b/src/tools/rustfmt/src/config/mod.rs index f49c18d3a..14f27f3f8 100644 --- a/src/tools/rustfmt/src/config/mod.rs +++ b/src/tools/rustfmt/src/config/mod.rs @@ -13,15 +13,20 @@ pub use crate::config::file_lines::{FileLines, FileName, Range}; #[allow(unreachable_pub)] pub use crate::config::lists::*; #[allow(unreachable_pub)] +pub use crate::config::macro_names::{MacroSelector, MacroSelectors}; +#[allow(unreachable_pub)] pub use crate::config::options::*; #[macro_use] pub(crate) mod config_type; #[macro_use] +#[allow(unreachable_pub)] pub(crate) mod options; pub(crate) mod file_lines; +#[allow(unreachable_pub)] pub(crate) mod lists; +pub(crate) mod macro_names; // This macro defines configuration options used in rustfmt. Each option // is defined as follows: @@ -67,6 +72,8 @@ create_config! { format_macro_matchers: bool, false, false, "Format the metavariable matching patterns in macros"; format_macro_bodies: bool, true, false, "Format the bodies of macros"; + skip_macro_invocations: MacroSelectors, MacroSelectors::default(), false, + "Skip formatting the bodies of macros invoked with the following names."; hex_literal_case: HexLiteralCase, HexLiteralCase::Preserve, false, "Format hexadecimal integer literals"; @@ -119,7 +126,9 @@ create_config! { force_multiline_blocks: bool, false, false, "Force multiline closure bodies and match arms to be wrapped in a block"; fn_args_layout: Density, Density::Tall, true, - "Control the layout of arguments in a function"; + "(deprecated: use fn_params_layout instead)"; + fn_params_layout: Density, Density::Tall, true, + "Control the layout of parameters in function signatures."; brace_style: BraceStyle, BraceStyle::SameLineWhere, false, "Brace style for items"; control_brace_style: ControlBraceStyle, ControlBraceStyle::AlwaysSameLine, false, "Brace style for control flow constructs"; @@ -175,7 +184,7 @@ create_config! { make_backup: bool, false, false, "Backup changed files"; print_misformatted_file_names: bool, false, true, "Prints the names of mismatched files that were formatted. Prints the names of \ - files that would be formated when used with `--check` mode. "; + files that would be formatted when used with `--check` mode. "; } #[derive(Error, Debug)] @@ -191,6 +200,7 @@ impl PartialConfig { cloned.width_heuristics = None; cloned.print_misformatted_file_names = None; cloned.merge_imports = None; + cloned.fn_args_layout = None; ::toml::to_string(&cloned).map_err(ToTomlError) } @@ -403,11 +413,21 @@ mod test { use super::*; use std::str; + use crate::config::macro_names::MacroName; use rustfmt_config_proc_macro::{nightly_only_test, stable_only_test}; #[allow(dead_code)] mod mock { use super::super::*; + use rustfmt_config_proc_macro::config_type; + + #[config_type] + pub(crate) enum PartiallyUnstableOption { + V1, + V2, + #[unstable_variant] + V3, + } create_config! { // Options that are used by the generated functions @@ -427,6 +447,12 @@ mod test { "Merge imports"; merge_imports: bool, false, false, "(deprecated: use imports_granularity instead)"; + // fn_args_layout renamed to fn_params_layout + fn_args_layout: Density, Density::Tall, true, + "(deprecated: use fn_params_layout instead)"; + fn_params_layout: Density, Density::Tall, true, + "Control the layout of parameters in a function signatures."; + // Width Heuristics use_small_heuristics: Heuristics, Heuristics::Default, true, "Whether to use different formatting for items and \ @@ -451,6 +477,63 @@ mod test { // Options that are used by the tests stable_option: bool, false, true, "A stable option"; unstable_option: bool, false, false, "An unstable option"; + partially_unstable_option: PartiallyUnstableOption, PartiallyUnstableOption::V1, true, + "A partially unstable option"; + } + + #[cfg(test)] + mod partially_unstable_option { + use super::{Config, PartialConfig, PartiallyUnstableOption}; + use rustfmt_config_proc_macro::{nightly_only_test, stable_only_test}; + use std::path::Path; + + /// From the config file, we can fill with a stable variant + #[test] + fn test_from_toml_stable_value() { + let toml = r#" + partially_unstable_option = "V2" + "#; + let partial_config: PartialConfig = toml::from_str(toml).unwrap(); + let config = Config::default(); + let config = config.fill_from_parsed_config(partial_config, Path::new("")); + assert_eq!( + config.partially_unstable_option(), + PartiallyUnstableOption::V2 + ); + } + + /// From the config file, we cannot fill with an unstable variant (stable only) + #[stable_only_test] + #[test] + fn test_from_toml_unstable_value_on_stable() { + let toml = r#" + partially_unstable_option = "V3" + "#; + let partial_config: PartialConfig = toml::from_str(toml).unwrap(); + let config = Config::default(); + let config = config.fill_from_parsed_config(partial_config, Path::new("")); + assert_eq!( + config.partially_unstable_option(), + // default value from config, i.e. fill failed + PartiallyUnstableOption::V1 + ); + } + + /// From the config file, we can fill with an unstable variant (nightly only) + #[nightly_only_test] + #[test] + fn test_from_toml_unstable_value_on_nightly() { + let toml = r#" + partially_unstable_option = "V3" + "#; + let partial_config: PartialConfig = toml::from_str(toml).unwrap(); + let config = Config::default(); + let config = config.fill_from_parsed_config(partial_config, Path::new("")); + assert_eq!( + config.partially_unstable_option(), + PartiallyUnstableOption::V3 + ); + } } } @@ -489,6 +572,11 @@ mod test { assert_eq!(config.was_set().verbose(), false); } + const PRINT_DOCS_STABLE_OPTION: &str = "stable_option <boolean> Default: false"; + const PRINT_DOCS_UNSTABLE_OPTION: &str = "unstable_option <boolean> Default: false (unstable)"; + const PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION: &str = + "partially_unstable_option [V1|V2|V3 (unstable)] Default: V1"; + #[test] fn test_print_docs_exclude_unstable() { use self::mock::Config; @@ -497,10 +585,9 @@ mod test { Config::print_docs(&mut output, false); let s = str::from_utf8(&output).unwrap(); - - assert_eq!(s.contains("stable_option"), true); - assert_eq!(s.contains("unstable_option"), false); - assert_eq!(s.contains("(unstable)"), false); + assert_eq!(s.contains(PRINT_DOCS_STABLE_OPTION), true); + assert_eq!(s.contains(PRINT_DOCS_UNSTABLE_OPTION), false); + assert_eq!(s.contains(PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION), true); } #[test] @@ -511,9 +598,9 @@ mod test { Config::print_docs(&mut output, true); let s = str::from_utf8(&output).unwrap(); - assert_eq!(s.contains("stable_option"), true); - assert_eq!(s.contains("unstable_option"), true); - assert_eq!(s.contains("(unstable)"), true); + assert_eq!(s.contains(PRINT_DOCS_STABLE_OPTION), true); + assert_eq!(s.contains(PRINT_DOCS_UNSTABLE_OPTION), true); + assert_eq!(s.contains(PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION), true); } #[test] @@ -541,6 +628,7 @@ normalize_doc_attributes = false format_strings = false format_macro_matchers = false format_macro_bodies = true +skip_macro_invocations = [] hex_literal_case = "Preserve" empty_item_single_line = true struct_lit_single_line = true @@ -567,7 +655,7 @@ enum_discrim_align_threshold = 0 match_arm_blocks = true match_arm_leading_pipes = "Never" force_multiline_blocks = false -fn_args_layout = "Tall" +fn_params_layout = "Tall" brace_style = "SameLineWhere" control_brace_style = "AlwaysSameLine" trailing_semicolon = true @@ -921,4 +1009,45 @@ make_backup = false assert_eq!(config.single_line_if_else_max_width(), 100); } } + + #[cfg(test)] + mod partially_unstable_option { + use super::mock::{Config, PartiallyUnstableOption}; + use super::*; + + /// From the command line, we can override with a stable variant. + #[test] + fn test_override_stable_value() { + let mut config = Config::default(); + config.override_value("partially_unstable_option", "V2"); + assert_eq!( + config.partially_unstable_option(), + PartiallyUnstableOption::V2 + ); + } + + /// From the command line, we can override with an unstable variant. + #[test] + fn test_override_unstable_value() { + let mut config = Config::default(); + config.override_value("partially_unstable_option", "V3"); + assert_eq!( + config.partially_unstable_option(), + PartiallyUnstableOption::V3 + ); + } + } + + #[test] + fn test_override_skip_macro_invocations() { + let mut config = Config::default(); + config.override_value("skip_macro_invocations", r#"["*", "println"]"#); + assert_eq!( + config.skip_macro_invocations(), + MacroSelectors(vec![ + MacroSelector::All, + MacroSelector::Name(MacroName::new("println".to_owned())) + ]) + ); + } } diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs index 868ff045a..3f0f217f8 100644 --- a/src/tools/rustfmt/src/expr.rs +++ b/src/tools/rustfmt/src/expr.rs @@ -29,9 +29,9 @@ use crate::spanned::Spanned; use crate::string::{rewrite_string, StringFormat}; use crate::types::{rewrite_path, PathContext}; use crate::utils::{ - colon_spaces, contains_skip, count_newlines, first_line_ends_with, inner_attributes, - last_line_extendable, last_line_width, mk_sp, outer_attributes, semicolon_for_expr, - unicode_str_width, wrap_str, + colon_spaces, contains_skip, count_newlines, filtered_str_fits, first_line_ends_with, + inner_attributes, last_line_extendable, last_line_width, mk_sp, outer_attributes, + semicolon_for_expr, unicode_str_width, wrap_str, }; use crate::vertical::rewrite_with_alignment; use crate::visitor::FmtVisitor; @@ -400,7 +400,10 @@ pub(crate) fn format_expr( } } ast::ExprKind::Underscore => Some("_".to_owned()), - ast::ExprKind::IncludedBytes(..) => unreachable!(), + ast::ExprKind::FormatArgs(..) | ast::ExprKind::IncludedBytes(..) => { + // These do not occur in the AST because macros aren't expanded. + unreachable!() + } ast::ExprKind::Err => None, }; @@ -2050,8 +2053,7 @@ fn choose_rhs<R: Rewrite>( match (orig_rhs, new_rhs) { (Some(ref orig_rhs), Some(ref new_rhs)) - if wrap_str(new_rhs.clone(), context.config.max_width(), new_shape) - .is_none() => + if !filtered_str_fits(&new_rhs, context.config.max_width(), new_shape) => { Some(format!("{}{}", before_space_str, orig_rhs)) } diff --git a/src/tools/rustfmt/src/imports.rs b/src/tools/rustfmt/src/imports.rs index d9dc8d004..339e5cef5 100644 --- a/src/tools/rustfmt/src/imports.rs +++ b/src/tools/rustfmt/src/imports.rs @@ -251,8 +251,8 @@ fn flatten_use_trees( use_trees: Vec<UseTree>, import_granularity: ImportGranularity, ) -> Vec<UseTree> { - // Return non-sorted single occurance of the use-trees text string; - // order is by first occurance of the use-tree. + // Return non-sorted single occurrence of the use-trees text string; + // order is by first occurrence of the use-tree. use_trees .into_iter() .flat_map(|tree| tree.flatten(import_granularity)) diff --git a/src/tools/rustfmt/src/items.rs b/src/tools/rustfmt/src/items.rs index a2a73f0a5..25e8a0248 100644 --- a/src/tools/rustfmt/src/items.rs +++ b/src/tools/rustfmt/src/items.rs @@ -1084,7 +1084,11 @@ pub(crate) fn format_trait( let item_snippet = context.snippet(item.span); if let Some(lo) = item_snippet.find('/') { // 1 = `{` - let comment_hi = body_lo - BytePos(1); + let comment_hi = if generics.params.len() > 0 { + generics.span.lo() - BytePos(1) + } else { + body_lo - BytePos(1) + }; let comment_lo = item.span.lo() + BytePos(lo as u32); if comment_lo < comment_hi { match recover_missing_comment_in_span( @@ -1241,7 +1245,7 @@ fn format_unit_struct( ) -> Option<String> { let header_str = format_header(context, p.prefix, p.ident, p.vis, offset); let generics_str = if let Some(generics) = p.generics { - let hi = context.snippet_provider.span_before(p.span, ";"); + let hi = context.snippet_provider.span_before_last(p.span, ";"); format_generics( context, generics, @@ -2602,7 +2606,7 @@ fn rewrite_params( ¶m_items, context .config - .fn_args_layout() + .fn_params_layout() .to_list_tactic(param_items.len()), Separator::Comma, one_line_budget, diff --git a/src/tools/rustfmt/src/lib.rs b/src/tools/rustfmt/src/lib.rs index 0c27bcacf..b27405efd 100644 --- a/src/tools/rustfmt/src/lib.rs +++ b/src/tools/rustfmt/src/lib.rs @@ -23,6 +23,7 @@ extern crate rustc_expand; extern crate rustc_parse; extern crate rustc_session; extern crate rustc_span; +extern crate thin_vec; // Necessary to pull in object code as the rest of the rustc crates are shipped only as rmeta // files. diff --git a/src/tools/rustfmt/src/lists.rs b/src/tools/rustfmt/src/lists.rs index e87850507..a878e6cf9 100644 --- a/src/tools/rustfmt/src/lists.rs +++ b/src/tools/rustfmt/src/lists.rs @@ -297,9 +297,9 @@ where } else { inner_item.as_ref() }; - let mut item_last_line_width = item_last_line.len() + item_sep_len; + let mut item_last_line_width = unicode_str_width(item_last_line) + item_sep_len; if item_last_line.starts_with(&**indent_str) { - item_last_line_width -= indent_str.len(); + item_last_line_width -= unicode_str_width(indent_str); } if !item.is_substantial() { @@ -449,7 +449,7 @@ where } else if starts_with_newline(comment) { false } else { - comment.trim().contains('\n') || comment.trim().len() > width + comment.trim().contains('\n') || unicode_str_width(comment.trim()) > width }; rewrite_comment( @@ -465,7 +465,7 @@ where if !starts_with_newline(comment) { if formatting.align_comments { let mut comment_alignment = - post_comment_alignment(item_max_width, inner_item.len()); + post_comment_alignment(item_max_width, unicode_str_width(inner_item)); if first_line_width(&formatted_comment) + last_line_width(&result) + comment_alignment @@ -475,7 +475,7 @@ where item_max_width = None; formatted_comment = rewrite_post_comment(&mut item_max_width)?; comment_alignment = - post_comment_alignment(item_max_width, inner_item.len()); + post_comment_alignment(item_max_width, unicode_str_width(inner_item)); } for _ in 0..=comment_alignment { result.push(' '); @@ -533,7 +533,7 @@ where let mut first = true; for item in items.clone().into_iter().skip(i) { let item = item.as_ref(); - let inner_item_width = item.inner_as_ref().len(); + let inner_item_width = unicode_str_width(item.inner_as_ref()); if !first && (item.is_different_group() || item.post_comment.is_none() @@ -552,8 +552,8 @@ where max_width } -fn post_comment_alignment(item_max_width: Option<usize>, inner_item_len: usize) -> usize { - item_max_width.unwrap_or(0).saturating_sub(inner_item_len) +fn post_comment_alignment(item_max_width: Option<usize>, inner_item_width: usize) -> usize { + item_max_width.unwrap_or(0).saturating_sub(inner_item_width) } pub(crate) struct ListItems<'a, I, F1, F2, F3> diff --git a/src/tools/rustfmt/src/macros.rs b/src/tools/rustfmt/src/macros.rs index df9493880..7978d8cba 100644 --- a/src/tools/rustfmt/src/macros.rs +++ b/src/tools/rustfmt/src/macros.rs @@ -13,7 +13,7 @@ use std::collections::HashMap; use std::panic::{catch_unwind, AssertUnwindSafe}; use rustc_ast::token::{BinOpToken, Delimiter, Token, TokenKind}; -use rustc_ast::tokenstream::{Cursor, TokenStream, TokenTree}; +use rustc_ast::tokenstream::{TokenStream, TokenTree, TokenTreeCursor}; use rustc_ast::{ast, ptr}; use rustc_ast_pretty::pprust; use rustc_span::{ @@ -35,8 +35,8 @@ use crate::shape::{Indent, Shape}; use crate::source_map::SpanUtils; use crate::spanned::Spanned; use crate::utils::{ - format_visibility, indent_next_line, is_empty_line, mk_sp, remove_trailing_white_spaces, - rewrite_ident, trim_left_preserve_layout, wrap_str, NodeIdExt, + filtered_str_fits, format_visibility, indent_next_line, is_empty_line, mk_sp, + remove_trailing_white_spaces, rewrite_ident, trim_left_preserve_layout, NodeIdExt, }; use crate::visitor::FmtVisitor; @@ -157,7 +157,8 @@ pub(crate) fn rewrite_macro( ) -> Option<String> { let should_skip = context .skip_context - .skip_macro(context.snippet(mac.path.span)); + .macros + .skip(context.snippet(mac.path.span)); if should_skip { None } else { @@ -735,7 +736,7 @@ impl MacroArgParser { self.buf.clear(); } - fn add_meta_variable(&mut self, iter: &mut Cursor) -> Option<()> { + fn add_meta_variable(&mut self, iter: &mut TokenTreeCursor) -> Option<()> { match iter.next() { Some(TokenTree::Token( Token { @@ -767,7 +768,7 @@ impl MacroArgParser { &mut self, inner: Vec<ParsedMacroArg>, delim: Delimiter, - iter: &mut Cursor, + iter: &mut TokenTreeCursor, ) -> Option<()> { let mut buffer = String::new(); let mut first = true; @@ -1120,7 +1121,7 @@ pub(crate) fn macro_style(mac: &ast::MacCall, context: &RewriteContext<'_>) -> D // Currently we do not attempt to parse any further than that. #[derive(new)] struct MacroParser { - toks: Cursor, + toks: TokenTreeCursor, } impl MacroParser { @@ -1265,15 +1266,14 @@ impl MacroBranch { } } }; - let new_body = wrap_str( - new_body_snippet.snippet.to_string(), - config.max_width(), - shape, - )?; + + if !filtered_str_fits(&new_body_snippet.snippet, config.max_width(), shape) { + return None; + } // Indent the body since it is in a block. let indent_str = body_indent.to_string(&config); - let mut new_body = LineClasses::new(new_body.trim_end()) + let mut new_body = LineClasses::new(new_body_snippet.snippet.trim_end()) .enumerate() .fold( (String::new(), true), diff --git a/src/tools/rustfmt/src/modules.rs b/src/tools/rustfmt/src/modules.rs index 7a0d1736c..af9a154a6 100644 --- a/src/tools/rustfmt/src/modules.rs +++ b/src/tools/rustfmt/src/modules.rs @@ -6,6 +6,7 @@ use rustc_ast::ast; use rustc_ast::visit::Visitor; use rustc_span::symbol::{self, sym, Symbol}; use rustc_span::Span; +use thin_vec::ThinVec; use thiserror::Error; use crate::attr::MetaVisitor; @@ -25,7 +26,7 @@ type FileModMap<'ast> = BTreeMap<FileName, Module<'ast>>; #[derive(Debug, Clone)] pub(crate) struct Module<'a> { ast_mod_kind: Option<Cow<'a, ast::ModKind>>, - pub(crate) items: Cow<'a, Vec<rustc_ast::ptr::P<ast::Item>>>, + pub(crate) items: Cow<'a, ThinVec<rustc_ast::ptr::P<ast::Item>>>, inner_attr: ast::AttrVec, pub(crate) span: Span, } @@ -34,7 +35,7 @@ impl<'a> Module<'a> { pub(crate) fn new( mod_span: Span, ast_mod_kind: Option<Cow<'a, ast::ModKind>>, - mod_items: Cow<'a, Vec<rustc_ast::ptr::P<ast::Item>>>, + mod_items: Cow<'a, ThinVec<rustc_ast::ptr::P<ast::Item>>>, mod_attrs: Cow<'a, ast::AttrVec>, ) -> Self { let inner_attr = mod_attrs @@ -157,7 +158,7 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { Module::new( module_item.item.span, Some(Cow::Owned(sub_mod_kind.clone())), - Cow::Owned(vec![]), + Cow::Owned(ThinVec::new()), Cow::Owned(ast::AttrVec::new()), ), )?; @@ -169,7 +170,7 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { /// Visit modules defined inside macro calls. fn visit_mod_outside_ast( &mut self, - items: Vec<rustc_ast::ptr::P<ast::Item>>, + items: ThinVec<rustc_ast::ptr::P<ast::Item>>, ) -> Result<(), ModuleResolutionError> { for item in items { if is_cfg_if(&item) { @@ -184,7 +185,7 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { Module::new( span, Some(Cow::Owned(sub_mod_kind.clone())), - Cow::Owned(vec![]), + Cow::Owned(ThinVec::new()), Cow::Owned(ast::AttrVec::new()), ), )?; @@ -210,7 +211,7 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { Module::new( span, Some(Cow::Borrowed(sub_mod_kind)), - Cow::Owned(vec![]), + Cow::Owned(ThinVec::new()), Cow::Borrowed(&item.attrs), ), )?; diff --git a/src/tools/rustfmt/src/parse/parser.rs b/src/tools/rustfmt/src/parse/parser.rs index e0bd06551..7ab042506 100644 --- a/src/tools/rustfmt/src/parse/parser.rs +++ b/src/tools/rustfmt/src/parse/parser.rs @@ -6,6 +6,7 @@ use rustc_ast::{ast, ptr}; use rustc_errors::Diagnostic; use rustc_parse::{new_parser_from_file, parser::Parser as RawParser}; use rustc_span::{sym, Span}; +use thin_vec::ThinVec; use crate::attr::first_attr_value_str_by_name; use crate::parse::session::ParseSess; @@ -109,7 +110,7 @@ impl<'a> Parser<'a> { sess: &'a ParseSess, path: &Path, span: Span, - ) -> Result<(ast::AttrVec, Vec<ptr::P<ast::Item>>, Span), ParserError> { + ) -> Result<(ast::AttrVec, ThinVec<ptr::P<ast::Item>>, Span), ParserError> { let result = catch_unwind(AssertUnwindSafe(|| { let mut parser = new_parser_from_file(sess.inner(), path, Some(span)); match parser.parse_mod(&TokenKind::Eof) { diff --git a/src/tools/rustfmt/src/parse/session.rs b/src/tools/rustfmt/src/parse/session.rs index 6bfec79cd..a64963db6 100644 --- a/src/tools/rustfmt/src/parse/session.rs +++ b/src/tools/rustfmt/src/parse/session.rs @@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use rustc_data_structures::sync::{Lrc, Send}; use rustc_errors::emitter::{Emitter, EmitterWriter}; use rustc_errors::translation::Translate; -use rustc_errors::{ColorConfig, Diagnostic, Handler, Level as DiagnosticLevel}; +use rustc_errors::{ColorConfig, Diagnostic, Handler, Level as DiagnosticLevel, TerminalUrl}; use rustc_session::parse::ParseSess as RawParseSess; use rustc_span::{ source_map::{FilePathMapping, SourceMap}, @@ -123,8 +123,10 @@ fn default_handler( let emitter = if hide_parse_errors { silent_emitter() } else { - let fallback_bundle = - rustc_errors::fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false); + let fallback_bundle = rustc_errors::fallback_fluent_bundle( + rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), + false, + ); Box::new(EmitterWriter::stderr( color_cfg, Some(source_map.clone()), @@ -135,6 +137,7 @@ fn default_handler( None, false, false, + TerminalUrl::No, )) }; Handler::with_emitter( diff --git a/src/tools/rustfmt/src/skip.rs b/src/tools/rustfmt/src/skip.rs index 032922d42..68f85b2ad 100644 --- a/src/tools/rustfmt/src/skip.rs +++ b/src/tools/rustfmt/src/skip.rs @@ -2,33 +2,84 @@ use rustc_ast::ast; use rustc_ast_pretty::pprust; +use std::collections::HashSet; -/// Take care of skip name stack. You can update it by attributes slice or -/// by other context. Query this context to know if you need skip a block. +/// Track which blocks of code are to be skipped when formatting. +/// +/// You can update it by: +/// +/// - attributes slice +/// - manually feeding values into the underlying contexts +/// +/// Query this context to know if you need to skip a block. #[derive(Default, Clone)] pub(crate) struct SkipContext { - macros: Vec<String>, - attributes: Vec<String>, + pub(crate) macros: SkipNameContext, + pub(crate) attributes: SkipNameContext, } impl SkipContext { pub(crate) fn update_with_attrs(&mut self, attrs: &[ast::Attribute]) { - self.macros.append(&mut get_skip_names("macros", attrs)); - self.attributes - .append(&mut get_skip_names("attributes", attrs)); + self.macros.extend(get_skip_names("macros", attrs)); + self.attributes.extend(get_skip_names("attributes", attrs)); } - pub(crate) fn update(&mut self, mut other: SkipContext) { - self.macros.append(&mut other.macros); - self.attributes.append(&mut other.attributes); + pub(crate) fn update(&mut self, other: SkipContext) { + let SkipContext { macros, attributes } = other; + self.macros.update(macros); + self.attributes.update(attributes); + } +} + +/// Track which names to skip. +/// +/// Query this context with a string to know whether to skip it. +#[derive(Clone)] +pub(crate) enum SkipNameContext { + All, + Values(HashSet<String>), +} + +impl Default for SkipNameContext { + fn default() -> Self { + Self::Values(Default::default()) + } +} + +impl Extend<String> for SkipNameContext { + fn extend<T: IntoIterator<Item = String>>(&mut self, iter: T) { + match self { + Self::All => {} + Self::Values(values) => values.extend(iter), + } + } +} + +impl SkipNameContext { + pub(crate) fn update(&mut self, other: Self) { + match (self, other) { + // If we're already skipping everything, nothing more can be added + (Self::All, _) => {} + // If we want to skip all, set it + (this, Self::All) => { + *this = Self::All; + } + // If we have some new values to skip, add them + (Self::Values(existing_values), Self::Values(new_values)) => { + existing_values.extend(new_values) + } + } } - pub(crate) fn skip_macro(&self, name: &str) -> bool { - self.macros.iter().any(|n| n == name) + pub(crate) fn skip(&self, name: &str) -> bool { + match self { + Self::All => true, + Self::Values(values) => values.contains(name), + } } - pub(crate) fn skip_attribute(&self, name: &str) -> bool { - self.attributes.iter().any(|n| n == name) + pub(crate) fn skip_all(&mut self) { + *self = Self::All; } } diff --git a/src/tools/rustfmt/src/test/configuration_snippet.rs b/src/tools/rustfmt/src/test/configuration_snippet.rs index c8fda7c85..c70b3c5fa 100644 --- a/src/tools/rustfmt/src/test/configuration_snippet.rs +++ b/src/tools/rustfmt/src/test/configuration_snippet.rs @@ -27,8 +27,13 @@ impl ConfigurationSection { lazy_static! { static ref CONFIG_NAME_REGEX: regex::Regex = regex::Regex::new(r"^## `([^`]+)`").expect("failed creating configuration pattern"); + // Configuration values, which will be passed to `from_str`: + // + // - must be prefixed with `####` + // - must be wrapped in backticks + // - may by wrapped in double quotes (which will be stripped) static ref CONFIG_VALUE_REGEX: regex::Regex = - regex::Regex::new(r#"^#### `"?([^`"]+)"?`"#) + regex::Regex::new(r#"^#### `"?([^`]+?)"?`"#) .expect("failed creating configuration value pattern"); } diff --git a/src/tools/rustfmt/src/test/mod.rs b/src/tools/rustfmt/src/test/mod.rs index 6b5bc2b30..cfad4a8ed 100644 --- a/src/tools/rustfmt/src/test/mod.rs +++ b/src/tools/rustfmt/src/test/mod.rs @@ -982,11 +982,7 @@ fn rustfmt() -> PathBuf { assert!( me.is_file() || me.with_extension("exe").is_file(), "{}", - if cfg!(release) { - "no rustfmt bin, try running `cargo build --release` before testing" - } else { - "no rustfmt bin, try running `cargo build` before testing" - } + "no rustfmt bin, try running `cargo build` or `cargo build --release` before testing" ); me } diff --git a/src/tools/rustfmt/src/types.rs b/src/tools/rustfmt/src/types.rs index c1991e8d2..01e2fb6e6 100644 --- a/src/tools/rustfmt/src/types.rs +++ b/src/tools/rustfmt/src/types.rs @@ -941,6 +941,28 @@ fn join_bounds_inner( ast::GenericBound::Trait(..) => last_line_extendable(s), }; + // Whether a GenericBound item is a PathSegment segment that includes internal array + // that contains more than one item + let is_item_with_multi_items_array = |item: &ast::GenericBound| match item { + ast::GenericBound::Trait(ref poly_trait_ref, ..) => { + let segments = &poly_trait_ref.trait_ref.path.segments; + if segments.len() > 1 { + true + } else { + if let Some(args_in) = &segments[0].args { + matches!( + args_in.deref(), + ast::GenericArgs::AngleBracketed(bracket_args) + if bracket_args.args.len() > 1 + ) + } else { + false + } + } + } + _ => false, + }; + let result = items.iter().enumerate().try_fold( (String::new(), None, false), |(strs, prev_trailing_span, prev_extendable), (i, item)| { @@ -1035,10 +1057,24 @@ fn join_bounds_inner( }, )?; - if !force_newline - && items.len() > 1 - && (result.0.contains('\n') || result.0.len() > shape.width) - { + // Whether to retry with a forced newline: + // Only if result is not already multiline and did not exceed line width, + // and either there is more than one item; + // or the single item is of type `Trait`, + // and any of the internal arrays contains more than one item; + let retry_with_force_newline = match context.config.version() { + Version::One => { + !force_newline + && items.len() > 1 + && (result.0.contains('\n') || result.0.len() > shape.width) + } + Version::Two if force_newline => false, + Version::Two if (!result.0.contains('\n') && result.0.len() <= shape.width) => false, + Version::Two if items.len() > 1 => true, + Version::Two => is_item_with_multi_items_array(&items[0]), + }; + + if retry_with_force_newline { join_bounds_inner(context, shape, items, need_indent, true) } else { Some(result.0) diff --git a/src/tools/rustfmt/src/utils.rs b/src/tools/rustfmt/src/utils.rs index 3e884419f..1e89f3ae7 100644 --- a/src/tools/rustfmt/src/utils.rs +++ b/src/tools/rustfmt/src/utils.rs @@ -384,14 +384,15 @@ macro_rules! skip_out_of_file_lines_range_visitor { // Wraps String in an Option. Returns Some when the string adheres to the // Rewrite constraints defined for the Rewrite trait and None otherwise. pub(crate) fn wrap_str(s: String, max_width: usize, shape: Shape) -> Option<String> { - if is_valid_str(&filter_normal_code(&s), max_width, shape) { + if filtered_str_fits(&s, max_width, shape) { Some(s) } else { None } } -fn is_valid_str(snippet: &str, max_width: usize, shape: Shape) -> bool { +pub(crate) fn filtered_str_fits(snippet: &str, max_width: usize, shape: Shape) -> bool { + let snippet = &filter_normal_code(snippet); if !snippet.is_empty() { // First line must fits with `shape.width`. if first_line_width(snippet) > shape.width { @@ -462,6 +463,7 @@ pub(crate) fn first_line_ends_with(s: &str, c: char) -> bool { pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr: &str) -> bool { match expr.kind { ast::ExprKind::MacCall(..) + | ast::ExprKind::FormatArgs(..) | ast::ExprKind::Call(..) | ast::ExprKind::MethodCall(..) | ast::ExprKind::Array(..) diff --git a/src/tools/rustfmt/src/visitor.rs b/src/tools/rustfmt/src/visitor.rs index 9c3cc7820..f4d84d138 100644 --- a/src/tools/rustfmt/src/visitor.rs +++ b/src/tools/rustfmt/src/visitor.rs @@ -8,7 +8,7 @@ use rustc_span::{symbol, BytePos, Pos, Span}; use crate::attr::*; use crate::comment::{contains_comment, rewrite_comment, CodeCharKind, CommentCodeSlices}; use crate::config::Version; -use crate::config::{BraceStyle, Config}; +use crate::config::{BraceStyle, Config, MacroSelector}; use crate::coverage::transform_missing_snippet; use crate::items::{ format_impl, format_trait, format_trait_alias, is_mod_decl, is_use_item, rewrite_extern_crate, @@ -770,6 +770,15 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { snippet_provider: &'a SnippetProvider, report: FormatReport, ) -> FmtVisitor<'a> { + let mut skip_context = SkipContext::default(); + let mut macro_names = Vec::new(); + for macro_selector in config.skip_macro_invocations().0 { + match macro_selector { + MacroSelector::Name(name) => macro_names.push(name.to_string()), + MacroSelector::All => skip_context.macros.skip_all(), + } + } + skip_context.macros.extend(macro_names); FmtVisitor { parent_context: None, parse_sess: parse_session, @@ -784,7 +793,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { is_macro_def: false, macro_rewrite_failure: false, report, - skip_context: Default::default(), + skip_context, } } diff --git a/src/tools/rustfmt/tests/cargo-fmt/main.rs b/src/tools/rustfmt/tests/cargo-fmt/main.rs index 348876cd2..701c36fad 100644 --- a/src/tools/rustfmt/tests/cargo-fmt/main.rs +++ b/src/tools/rustfmt/tests/cargo-fmt/main.rs @@ -4,6 +4,8 @@ use std::env; use std::path::Path; use std::process::Command; +use rustfmt_config_proc_macro::rustfmt_only_ci_test; + /// Run the cargo-fmt executable and return its output. fn cargo_fmt(args: &[&str]) -> (String, String) { let mut bin_dir = env::current_exe().unwrap(); @@ -47,7 +49,7 @@ macro_rules! assert_that { }; } -#[ignore] +#[rustfmt_only_ci_test] #[test] fn version() { assert_that!(&["--version"], starts_with("rustfmt ")); @@ -56,7 +58,7 @@ fn version() { assert_that!(&["--", "--version"], starts_with("rustfmt ")); } -#[ignore] +#[rustfmt_only_ci_test] #[test] fn print_config() { assert_that!( @@ -65,7 +67,7 @@ fn print_config() { ); } -#[ignore] +#[rustfmt_only_ci_test] #[test] fn rustfmt_help() { assert_that!(&["--", "--help"], contains("Format Rust code")); @@ -73,7 +75,7 @@ fn rustfmt_help() { assert_that!(&["--", "--help=config"], contains("Configuration Options:")); } -#[ignore] +#[rustfmt_only_ci_test] #[test] fn cargo_fmt_out_of_line_test_modules() { // See also https://github.com/rust-lang/rustfmt/issues/5119 @@ -96,3 +98,22 @@ fn cargo_fmt_out_of_line_test_modules() { assert!(stdout.contains(&format!("Diff in {}", path.display()))) } } + +#[rustfmt_only_ci_test] +#[test] +fn cargo_fmt_emits_error_on_line_overflow_true() { + // See also https://github.com/rust-lang/rustfmt/issues/3164 + let args = [ + "--check", + "--manifest-path", + "tests/cargo-fmt/source/issue_3164/Cargo.toml", + "--", + "--config", + "error_on_line_overflow=true", + ]; + + let (_stdout, stderr) = cargo_fmt(&args); + assert!(stderr.contains( + "line formatted, but exceeded maximum width (maximum: 100 (see `max_width` option)" + )) +} diff --git a/src/tools/rustfmt/tests/cargo-fmt/source/issue_3164/Cargo.toml b/src/tools/rustfmt/tests/cargo-fmt/source/issue_3164/Cargo.toml new file mode 100644 index 000000000..580ef7e6e --- /dev/null +++ b/src/tools/rustfmt/tests/cargo-fmt/source/issue_3164/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "issue_3164" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/tools/rustfmt/tests/cargo-fmt/source/issue_3164/src/main.rs b/src/tools/rustfmt/tests/cargo-fmt/source/issue_3164/src/main.rs new file mode 100644 index 000000000..9330107ac --- /dev/null +++ b/src/tools/rustfmt/tests/cargo-fmt/source/issue_3164/src/main.rs @@ -0,0 +1,13 @@ +#[allow(unused_macros)] +macro_rules! foo { + ($id:ident) => { + macro_rules! bar { + ($id2:tt) => { + #[cfg(any(target_feature = $id2, target_feature = $id2, target_feature = $id2, target_feature = $id2, target_feature = $id2))] + fn $id() {} + }; + } + }; +} + +fn main() {} diff --git a/src/tools/rustfmt/tests/config/small_tabs.toml b/src/tools/rustfmt/tests/config/small_tabs.toml index c3cfd3431..4c3710089 100644 --- a/src/tools/rustfmt/tests/config/small_tabs.toml +++ b/src/tools/rustfmt/tests/config/small_tabs.toml @@ -3,7 +3,7 @@ comment_width = 80 tab_spaces = 2 newline_style = "Unix" brace_style = "SameLineWhere" -fn_args_layout = "Tall" +fn_params_layout = "Tall" trailing_comma = "Vertical" indent_style = "Block" reorder_imports = false diff --git a/src/tools/rustfmt/tests/mod-resolver/issue-5198/lib/c/d/explanation.txt b/src/tools/rustfmt/tests/mod-resolver/issue-5198/lib/c/d/explanation.txt index 92c9e3021..254102eba 100644 --- a/src/tools/rustfmt/tests/mod-resolver/issue-5198/lib/c/d/explanation.txt +++ b/src/tools/rustfmt/tests/mod-resolver/issue-5198/lib/c/d/explanation.txt @@ -9,7 +9,7 @@ The directory name './lib/c/d/' conflicts with the './lib/c/d.rs' file name. * mod g; Module resolution will fail if we look for './lib/c/d/e.rs' or './lib/c/d/e/mod.rs', -so we should fall back to looking for './lib/c/e.rs', which correctly finds the modlue, that +so we should fall back to looking for './lib/c/e.rs', which correctly finds the module, that rustfmt should format. './lib/c/d/f.rs' and './lib/c/d/g/mod.rs' exist at the default submodule paths so we should be able diff --git a/src/tools/rustfmt/tests/mod-resolver/issue-5198/lib/explanation.txt b/src/tools/rustfmt/tests/mod-resolver/issue-5198/lib/explanation.txt index d436a8076..90464def8 100644 --- a/src/tools/rustfmt/tests/mod-resolver/issue-5198/lib/explanation.txt +++ b/src/tools/rustfmt/tests/mod-resolver/issue-5198/lib/explanation.txt @@ -9,7 +9,7 @@ The directory name './lib' conflicts with the './lib.rs' file name. * mod c; Module resolution will fail if we look for './lib/a.rs' or './lib/a/mod.rs', -so we should fall back to looking for './a.rs', which correctly finds the modlue that +so we should fall back to looking for './a.rs', which correctly finds the module that rustfmt should format. './lib/b.rs' and './lib/c/mod.rs' exist at the default submodule paths so we should be able diff --git a/src/tools/rustfmt/tests/rustfmt/main.rs b/src/tools/rustfmt/tests/rustfmt/main.rs index 4c6d52726..7ff301e80 100644 --- a/src/tools/rustfmt/tests/rustfmt/main.rs +++ b/src/tools/rustfmt/tests/rustfmt/main.rs @@ -5,6 +5,8 @@ use std::fs::remove_file; use std::path::Path; use std::process::Command; +use rustfmt_config_proc_macro::rustfmt_only_ci_test; + /// Run the rustfmt executable and return its output. fn rustfmt(args: &[&str]) -> (String, String) { let mut bin_dir = env::current_exe().unwrap(); @@ -47,7 +49,7 @@ macro_rules! assert_that { }; } -#[ignore] +#[rustfmt_only_ci_test] #[test] fn print_config() { assert_that!( @@ -76,7 +78,7 @@ fn print_config() { remove_file("minimal-config").unwrap(); } -#[ignore] +#[rustfmt_only_ci_test] #[test] fn inline_config() { // single invocation @@ -157,3 +159,18 @@ fn mod_resolution_error_path_attribute_does_not_exist() { // The path attribute points to a file that does not exist assert!(stderr.contains("does_not_exist.rs does not exist")); } + +#[test] +fn rustfmt_emits_error_on_line_overflow_true() { + // See also https://github.com/rust-lang/rustfmt/issues/3164 + let args = [ + "--config", + "error_on_line_overflow=true", + "tests/cargo-fmt/source/issue_3164/src/main.rs", + ]; + + let (_stdout, stderr) = rustfmt(&args); + assert!(stderr.contains( + "line formatted, but exceeded maximum width (maximum: 100 (see `max_width` option)" + )) +} diff --git a/src/tools/rustfmt/tests/source/cfg_if/detect/arch/x86.rs b/src/tools/rustfmt/tests/source/cfg_if/detect/arch/x86.rs index d26f4ee89..131cbb855 100644 --- a/src/tools/rustfmt/tests/source/cfg_if/detect/arch/x86.rs +++ b/src/tools/rustfmt/tests/source/cfg_if/detect/arch/x86.rs @@ -329,7 +329,7 @@ pub enum Feature { tbm, /// POPCNT (Population Count) popcnt, - /// FXSR (Floating-point context fast save and restor) + /// FXSR (Floating-point context fast save and restore) fxsr, /// XSAVE (Save Processor Extended States) xsave, diff --git a/src/tools/rustfmt/tests/source/comments_unicode.rs b/src/tools/rustfmt/tests/source/comments_unicode.rs new file mode 100644 index 000000000..e65a245ba --- /dev/null +++ b/src/tools/rustfmt/tests/source/comments_unicode.rs @@ -0,0 +1,140 @@ +impl Default for WhitespaceCharacters { + fn default() -> Self { + Self { + space: '·', // U+00B7 + nbsp: '⍽', // U+237D + tab: '→', // U+2192 + newline: '⏎', // U+23CE + } + } +} + +const RAINBOWS: &[&str] = &[ + "rаinЬοѡ", // hue: 0 + "raіnЬοw", // hue: 2 + "rаіɴЬow", // hue: 2 + "raіɴЬoѡ", // hue: 8 + "ʀainЬow", // hue: 8 + "ʀaіɴboѡ", // hue: 8 + "ʀаіnbοw", // hue: 11 + "rainЬoѡ", // hue: 14 + "raіɴbow", // hue: 14 + "rаiɴЬow", // hue: 20 + "raіnЬow", // hue: 26 + "ʀaiɴbοw", // hue: 32 + "raіɴboѡ", // hue: 35 + "rаiɴbow", // hue: 35 + "rаіnbοw", // hue: 38 + "rаinЬow", // hue: 47 + "ʀaіnboѡ", // hue: 47 + "ʀaіnЬoѡ", // hue: 47 + "ʀаіɴbοw", // hue: 53 + "ʀaіnЬοѡ", // hue: 57 + "raiɴЬoѡ", // hue: 68 + "ʀainbοѡ", // hue: 68 + "ʀаinboѡ", // hue: 68 + "ʀаiɴbοw", // hue: 68 + "ʀаіnbow", // hue: 68 + "rаіnЬοѡ", // hue: 69 + "ʀainЬοw", // hue: 71 + "raiɴbow", // hue: 73 + "raіnЬoѡ", // hue: 74 + "rаіɴbοw", // hue: 77 + "raіnЬοѡ", // hue: 81 + "raiɴЬow", // hue: 83 + "ʀainbοw", // hue: 83 + "ʀаinbow", // hue: 83 + "ʀаiɴbοѡ", // hue: 83 + "ʀаіnboѡ", // hue: 83 + "ʀаіɴЬοѡ", // hue: 84 + "rainЬow", // hue: 85 + "ʀаiɴЬοw", // hue: 86 + "ʀаіnbοѡ", // hue: 89 + "ʀаіnЬοw", // hue: 92 + "rаiɴbοw", // hue: 95 + "ʀаіɴbοѡ", // hue: 98 + "ʀаiɴЬοѡ", // hue: 99 + "raіnbοw", // hue: 101 + "ʀаіɴЬοw", // hue: 101 + "ʀaiɴboѡ", // hue: 104 + "ʀаinbοѡ", // hue: 104 + "rаiɴbοѡ", // hue: 107 + "ʀаinЬοw", // hue: 107 + "rаiɴЬοw", // hue: 110 + "rаіnboѡ", // hue: 110 + "rаіnbοѡ", // hue: 113 + "ʀainЬοѡ", // hue: 114 + "rаіnЬοw", // hue: 116 + "ʀaіɴЬow", // hue: 116 + "rаinbοw", // hue: 122 + "ʀаіɴboѡ", // hue: 125 + "rаinbοѡ", // hue: 131 + "rainbow", // hue: 134 + "rаinЬοw", // hue: 134 + "ʀаiɴboѡ", // hue: 140 + "rainЬοѡ", // hue: 141 + "raіɴЬow", // hue: 143 + "ʀainЬoѡ", // hue: 143 + "ʀaіɴbow", // hue: 143 + "ʀainbow", // hue: 148 + "rаіɴboѡ", // hue: 149 + "ʀainboѡ", // hue: 155 + "ʀaіnbow", // hue: 155 + "ʀaіnЬow", // hue: 155 + "raiɴbοw", // hue: 158 + "ʀаiɴЬoѡ", // hue: 158 + "rainbοw", // hue: 160 + "rаinbow", // hue: 160 + "ʀaіɴbοѡ", // hue: 164 + "ʀаiɴbow", // hue: 164 + "ʀаіnЬoѡ", // hue: 164 + "ʀaiɴЬοѡ", // hue: 165 + "rаiɴboѡ", // hue: 167 + "ʀaіɴЬοw", // hue: 167 + "ʀaіɴЬοѡ", // hue: 171 + "raіnboѡ", // hue: 173 + "ʀаіɴЬoѡ", // hue: 173 + "rаіɴbοѡ", // hue: 176 + "ʀаinЬow", // hue: 176 + "rаiɴЬοѡ", // hue: 177 + "rаіɴЬοw", // hue: 179 + "ʀаinЬoѡ", // hue: 179 + "ʀаіɴbow", // hue: 179 + "rаiɴЬoѡ", // hue: 182 + "raіɴbοѡ", // hue: 188 + "rаіnЬoѡ", // hue: 188 + "raiɴЬοѡ", // hue: 189 + "raіɴЬοw", // hue: 191 + "ʀaіɴbοw", // hue: 191 + "ʀаіnЬow", // hue: 191 + "rainbοѡ", // hue: 194 + "rаinboѡ", // hue: 194 + "rаіnbow", // hue: 194 + "rainЬοw", // hue: 197 + "rаinЬoѡ", // hue: 206 + "rаіɴbow", // hue: 206 + "rаіɴЬοѡ", // hue: 210 + "ʀaiɴЬow", // hue: 212 + "raіɴbοw", // hue: 218 + "rаіnЬow", // hue: 218 + "ʀaiɴbοѡ", // hue: 221 + "ʀaiɴЬοw", // hue: 224 + "ʀaіnbοѡ", // hue: 227 + "raiɴboѡ", // hue: 230 + "ʀaіnbοw", // hue: 230 + "ʀaіnЬοw", // hue: 230 + "ʀаinЬοѡ", // hue: 231 + "rainboѡ", // hue: 232 + "raіnbow", // hue: 232 + "ʀаіɴЬow", // hue: 233 + "ʀaіɴЬoѡ", // hue: 239 + "ʀаіnЬοѡ", // hue: 246 + "raiɴbοѡ", // hue: 248 + "ʀаiɴЬow", // hue: 248 + "raіɴЬοѡ", // hue: 249 + "raiɴЬοw", // hue: 251 + "rаіɴЬoѡ", // hue: 251 + "ʀaiɴbow", // hue: 251 + "ʀаinbοw", // hue: 251 + "raіnbοѡ", // hue: 254 +]; diff --git a/src/tools/rustfmt/tests/source/configs/fn_args_layout/compressed.rs b/src/tools/rustfmt/tests/source/configs/fn_params_layout/compressed.rs index 66a371c25..eb573d312 100644 --- a/src/tools/rustfmt/tests/source/configs/fn_args_layout/compressed.rs +++ b/src/tools/rustfmt/tests/source/configs/fn_params_layout/compressed.rs @@ -1,4 +1,4 @@ -// rustfmt-fn_args_layout: Compressed +// rustfmt-fn_params_layout: Compressed // Function arguments density trait Lorem { diff --git a/src/tools/rustfmt/tests/source/configs/fn_args_layout/tall.rs b/src/tools/rustfmt/tests/source/configs/fn_params_layout/tall.rs index f11e86fd3..4be34f0fe 100644 --- a/src/tools/rustfmt/tests/source/configs/fn_args_layout/tall.rs +++ b/src/tools/rustfmt/tests/source/configs/fn_params_layout/tall.rs @@ -1,4 +1,4 @@ -// rustfmt-fn_args_layout: Tall +// rustfmt-fn_params_layout: Tall // Function arguments density trait Lorem { diff --git a/src/tools/rustfmt/tests/source/configs/fn_args_layout/vertical.rs b/src/tools/rustfmt/tests/source/configs/fn_params_layout/vertical.rs index a23cc0252..674968023 100644 --- a/src/tools/rustfmt/tests/source/configs/fn_args_layout/vertical.rs +++ b/src/tools/rustfmt/tests/source/configs/fn_params_layout/vertical.rs @@ -1,4 +1,4 @@ -// rustfmt-fn_args_layout: Vertical +// rustfmt-fn_params_layout: Vertical // Function arguments density trait Lorem { diff --git a/src/tools/rustfmt/tests/source/enum.rs b/src/tools/rustfmt/tests/source/enum.rs index 0ed9651ab..a7b961692 100644 --- a/src/tools/rustfmt/tests/source/enum.rs +++ b/src/tools/rustfmt/tests/source/enum.rs @@ -36,7 +36,7 @@ enum StructLikeVariants { Normal(u32, String, ), StructLike { x: i32, // Test comment // Pre-comment - #[Attr50] y: SomeType, // Aanother Comment + #[Attr50] y: SomeType, // Another Comment }, SL { a: A } } diff --git a/src/tools/rustfmt/tests/source/fn-custom-7.rs b/src/tools/rustfmt/tests/source/fn-custom-7.rs index d5330196b..3ecd87017 100644 --- a/src/tools/rustfmt/tests/source/fn-custom-7.rs +++ b/src/tools/rustfmt/tests/source/fn-custom-7.rs @@ -1,5 +1,5 @@ // rustfmt-normalize_comments: true -// rustfmt-fn_args_layout: Vertical +// rustfmt-fn_params_layout: Vertical // rustfmt-brace_style: AlwaysNextLine // Case with only one variable. diff --git a/src/tools/rustfmt/tests/source/fn-custom.rs b/src/tools/rustfmt/tests/source/fn-custom.rs index 77ced4c5e..64ef0ecfa 100644 --- a/src/tools/rustfmt/tests/source/fn-custom.rs +++ b/src/tools/rustfmt/tests/source/fn-custom.rs @@ -1,4 +1,4 @@ -// rustfmt-fn_args_layout: Compressed +// rustfmt-fn_params_layout: Compressed // Test some of the ways function signatures can be customised. // Test compressed layout of args. diff --git a/src/tools/rustfmt/tests/source/fn_args_layout-vertical.rs b/src/tools/rustfmt/tests/source/fn_args_layout-vertical.rs index 759bc83d0..fd6e3f044 100644 --- a/src/tools/rustfmt/tests/source/fn_args_layout-vertical.rs +++ b/src/tools/rustfmt/tests/source/fn_args_layout-vertical.rs @@ -1,4 +1,4 @@ -// rustfmt-fn_args_layout: Vertical +// rustfmt-fn_params_layout: Vertical // Empty list should stay on one line. fn do_bar( diff --git a/src/tools/rustfmt/tests/source/issue-3987/format_macro_bodies_true.rs b/src/tools/rustfmt/tests/source/issue-3987/format_macro_bodies_true.rs new file mode 100644 index 000000000..9af114fbe --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-3987/format_macro_bodies_true.rs @@ -0,0 +1,26 @@ +// rustfmt-format_macro_bodies: true + +// with comments +macro_rules! macros { + () => {{ + Struct { + field: ( + 42 + //comment 1 + 42 + //comment 2 + ), + }; + }}; +} + +// without comments +macro_rules! macros { + () => {{ + Struct { + field: ( + 42 + + 42 + ), + }; + }}; +} diff --git a/src/tools/rustfmt/tests/source/issue-4643.rs b/src/tools/rustfmt/tests/source/issue-4643.rs new file mode 100644 index 000000000..382072d90 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-4643.rs @@ -0,0 +1,23 @@ +// output doesn't get corrupted when using comments within generic type parameters of a trait + +pub trait Something< + A, + // some comment + B, + C +> { + fn a(&self, x: A) -> i32; + fn b(&self, x: B) -> i32; + fn c(&self, x: C) -> i32; +} + +pub trait SomethingElse< + A, + /* some comment */ + B, + C +> { + fn a(&self, x: A) -> i32; + fn b(&self, x: B) -> i32; + fn c(&self, x: C) -> i32; +} diff --git a/src/tools/rustfmt/tests/source/issue-4689/one.rs b/src/tools/rustfmt/tests/source/issue-4689/one.rs new file mode 100644 index 000000000..d048eb10f --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-4689/one.rs @@ -0,0 +1,149 @@ +// rustfmt-version: One + +// Based on the issue description +pub trait PrettyPrinter<'tcx>: +Printer< +'tcx, +Error = fmt::Error, +Path = Self, +Region = Self, +Type = Self, +DynExistential = Self, +Const = Self, +> +{ +// +} +pub trait PrettyPrinter<'tcx>: +Printer< +'tcx, +Error = fmt::Error, +Path = Self, +Region = Self, +Type = Self, +DynExistential = Self, +Const = Self, +> + fmt::Write +{ +// +} +pub trait PrettyPrinter<'tcx>: +Printer< +'tcx, +Error = fmt::Error, +Path = Self, +Region = Self, +Type = Self, +DynExistential = Self, +Const = Self, +> + fmt::Write1 + fmt::Write2 +{ +// +} +pub trait PrettyPrinter<'tcx>: +fmt::Write + Printer< +'tcx, +Error = fmt::Error, +Path = Self, +Region = Self, +Type = Self, +DynExistential = Self, +Const = Self, +> +{ +// +} +pub trait PrettyPrinter<'tcx>: +fmt::Write + Printer1< +'tcx, +Error = fmt::Error, +Path = Self, +Region = Self, +Type = Self, +DynExistential = Self, +Const = Self, +> + Printer2< +'tcx, +Error = fmt::Error, +Path = Self, +Region = Self, +Type = Self, +DynExistential = Self, +Const = Self, +> +{ +// +} + +// Some test cases to ensure other cases formatting were not changed +fn f() -> Box< +FnMut() -> Thing< +WithType = LongItemName, +Error = LONGLONGLONGLONGLONGONGEvenLongerErrorNameLongerLonger, +>, +> { +} +fn f() -> Box< +FnMut() -> Thing< +WithType = LongItemName, +Error = LONGLONGLONGLONGLONGONGEvenLongerErrorNameLongerLonger, +> + fmt::Write1 ++ fmt::Write2, +> { +} + +fn foo<F>(foo2: F) +where +F: Fn( +// this comment is deleted +) +{ +} +fn foo<F>(foo2: F) +where +F: Fn( +// this comment is deleted +) + fmt::Write +{ +} + +fn elaborate_bounds<F>(mut mk_cand: F) +where +F: for<> FnMut( +&mut ProbeContext<>, +ty::PolyTraitRefffffffffffffffffffffffffffffffff<>, +tyyyyyyyyyyyyyyyyyyyyy::AssociatedItem, +), +{ +} +fn elaborate_bounds<F>(mut mk_cand: F) +where +F: for<> FnMut( +&mut ProbeContext<>, +ty::PolyTraitRefffffffffffffffffffffffffffffffff<>, +tyyyyyyyyyyyyyyyyyyyyy::AssociatedItem, +) + fmt::Write, +{ +} + +fn build_sorted_static_get_entry_names( +mut entries: entryyyyyyyy, +) -> ( +impl Fn( +AlphabeticalTraversal, +Seconddddddddddddddddddddddddddddddddddd +) -> Parammmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm ++ Sendddddddddddddddddddddddddddddddddddddddddddd +) { +} + +pub trait SomeTrait: +Cloneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee ++ Eqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq +{ +} + +trait B = where +for<'b> &'b Self: Send ++ Cloneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee ++ Copyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy; diff --git a/src/tools/rustfmt/tests/source/issue-4689/two.rs b/src/tools/rustfmt/tests/source/issue-4689/two.rs new file mode 100644 index 000000000..ea7feda82 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue-4689/two.rs @@ -0,0 +1,149 @@ +// rustfmt-version: Two + +// Based on the issue description +pub trait PrettyPrinter<'tcx>: +Printer< +'tcx, +Error = fmt::Error, +Path = Self, +Region = Self, +Type = Self, +DynExistential = Self, +Const = Self, +> +{ +// +} +pub trait PrettyPrinter<'tcx>: +Printer< +'tcx, +Error = fmt::Error, +Path = Self, +Region = Self, +Type = Self, +DynExistential = Self, +Const = Self, +> + fmt::Write +{ +// +} +pub trait PrettyPrinter<'tcx>: +Printer< +'tcx, +Error = fmt::Error, +Path = Self, +Region = Self, +Type = Self, +DynExistential = Self, +Const = Self, +> + fmt::Write1 + fmt::Write2 +{ +// +} +pub trait PrettyPrinter<'tcx>: +fmt::Write + Printer< +'tcx, +Error = fmt::Error, +Path = Self, +Region = Self, +Type = Self, +DynExistential = Self, +Const = Self, +> +{ +// +} +pub trait PrettyPrinter<'tcx>: +fmt::Write + Printer1< +'tcx, +Error = fmt::Error, +Path = Self, +Region = Self, +Type = Self, +DynExistential = Self, +Const = Self, +> + Printer2< +'tcx, +Error = fmt::Error, +Path = Self, +Region = Self, +Type = Self, +DynExistential = Self, +Const = Self, +> +{ +// +} + +// Some test cases to ensure other cases formatting were not changed +fn f() -> Box< +FnMut() -> Thing< +WithType = LongItemName, +Error = LONGLONGLONGLONGLONGONGEvenLongerErrorNameLongerLonger, +>, +> { +} +fn f() -> Box< +FnMut() -> Thing< +WithType = LongItemName, +Error = LONGLONGLONGLONGLONGONGEvenLongerErrorNameLongerLonger, +> + fmt::Write1 ++ fmt::Write2, +> { +} + +fn foo<F>(foo2: F) +where +F: Fn( +// this comment is deleted +) +{ +} +fn foo<F>(foo2: F) +where +F: Fn( +// this comment is deleted +) + fmt::Write +{ +} + +fn elaborate_bounds<F>(mut mk_cand: F) +where +F: for<> FnMut( +&mut ProbeContext<>, +ty::PolyTraitRefffffffffffffffffffffffffffffffff<>, +tyyyyyyyyyyyyyyyyyyyyy::AssociatedItem, +), +{ +} +fn elaborate_bounds<F>(mut mk_cand: F) +where +F: for<> FnMut( +&mut ProbeContext<>, +ty::PolyTraitRefffffffffffffffffffffffffffffffff<>, +tyyyyyyyyyyyyyyyyyyyyy::AssociatedItem, +) + fmt::Write, +{ +} + +fn build_sorted_static_get_entry_names( +mut entries: entryyyyyyyy, +) -> ( +impl Fn( +AlphabeticalTraversal, +Seconddddddddddddddddddddddddddddddddddd +) -> Parammmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm ++ Sendddddddddddddddddddddddddddddddddddddddddddd +) { +} + +pub trait SomeTrait: +Cloneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee ++ Eqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq +{ +} + +trait B = where +for<'b> &'b Self: Send ++ Cloneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee ++ Copyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy; diff --git a/src/tools/rustfmt/tests/source/issue_1306.rs b/src/tools/rustfmt/tests/source/issue_1306.rs new file mode 100644 index 000000000..03b78e341 --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue_1306.rs @@ -0,0 +1,29 @@ +// rustfmt-max_width: 160 +// rustfmt-fn_call_width: 96 +// rustfmt-fn_args_layout: Compressed +// rustfmt-trailing_comma: Always +// rustfmt-wrap_comments: true + +fn foo() { + for elem in try!(gen_epub_book::ops::parse_descriptor_file(&mut try!(File::open(&opts.source_file.1).map_err(|_| { + gen_epub_book::Error::Io { + desc: "input file", + op: "open", + more: None, + } + })), + "input file")) { + println!("{}", elem); + } +} + +fn write_content() { + io::copy(try!(File::open(in_f).map_err(|_| { + Error::Io { + desc: "Content", + op: "open", + more: None, + } + })), + w); +} diff --git a/src/tools/rustfmt/tests/source/issue_3245.rs b/src/tools/rustfmt/tests/source/issue_3245.rs new file mode 100644 index 000000000..0279246ed --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue_3245.rs @@ -0,0 +1,4 @@ +fn main() { + let x = 1; + ;let y = 3; +} diff --git a/src/tools/rustfmt/tests/source/issue_3561.rs b/src/tools/rustfmt/tests/source/issue_3561.rs new file mode 100644 index 000000000..8f6cd8f9f --- /dev/null +++ b/src/tools/rustfmt/tests/source/issue_3561.rs @@ -0,0 +1,6 @@ +fn main() {;7 +} + +fn main() { + ;7 +} diff --git a/src/tools/rustfmt/tests/source/skip_macro_invocations/all.rs b/src/tools/rustfmt/tests/source/skip_macro_invocations/all.rs new file mode 100644 index 000000000..d0437ee10 --- /dev/null +++ b/src/tools/rustfmt/tests/source/skip_macro_invocations/all.rs @@ -0,0 +1,11 @@ +// rustfmt-skip_macro_invocations: ["*"] + +// Should skip this invocation +items!( + const _: u8 = 0; +); + +// Should skip this invocation +renamed_items!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/source/skip_macro_invocations/all_and_name.rs b/src/tools/rustfmt/tests/source/skip_macro_invocations/all_and_name.rs new file mode 100644 index 000000000..1f6722344 --- /dev/null +++ b/src/tools/rustfmt/tests/source/skip_macro_invocations/all_and_name.rs @@ -0,0 +1,11 @@ +// rustfmt-skip_macro_invocations: ["*","items"] + +// Should skip this invocation +items!( + const _: u8 = 0; +); + +// Should also skip this invocation, as the wildcard covers it +renamed_items!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/source/skip_macro_invocations/empty.rs b/src/tools/rustfmt/tests/source/skip_macro_invocations/empty.rs new file mode 100644 index 000000000..f3dd89dc4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/skip_macro_invocations/empty.rs @@ -0,0 +1,11 @@ +// rustfmt-skip_macro_invocations: [] + +// Should not skip this invocation +items!( + const _: u8 = 0; +); + +// Should not skip this invocation +renamed_items!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/source/skip_macro_invocations/name.rs b/src/tools/rustfmt/tests/source/skip_macro_invocations/name.rs new file mode 100644 index 000000000..7fa5d3a6f --- /dev/null +++ b/src/tools/rustfmt/tests/source/skip_macro_invocations/name.rs @@ -0,0 +1,11 @@ +// rustfmt-skip_macro_invocations: ["items"] + +// Should skip this invocation +items!( + const _: u8 = 0; +); + +// Should not skip this invocation +renamed_items!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/source/skip_macro_invocations/name_unknown.rs b/src/tools/rustfmt/tests/source/skip_macro_invocations/name_unknown.rs new file mode 100644 index 000000000..d56695325 --- /dev/null +++ b/src/tools/rustfmt/tests/source/skip_macro_invocations/name_unknown.rs @@ -0,0 +1,6 @@ +// rustfmt-skip_macro_invocations: ["unknown"] + +// Should not skip this invocation +items!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/source/skip_macro_invocations/names.rs b/src/tools/rustfmt/tests/source/skip_macro_invocations/names.rs new file mode 100644 index 000000000..a920381a4 --- /dev/null +++ b/src/tools/rustfmt/tests/source/skip_macro_invocations/names.rs @@ -0,0 +1,16 @@ +// rustfmt-skip_macro_invocations: ["foo","bar"] + +// Should skip this invocation +foo!( + const _: u8 = 0; +); + +// Should skip this invocation +bar!( + const _: u8 = 0; +); + +// Should not skip this invocation +baz!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/source/skip_macro_invocations/path_qualified_invocation_mismatch.rs b/src/tools/rustfmt/tests/source/skip_macro_invocations/path_qualified_invocation_mismatch.rs new file mode 100644 index 000000000..61296869a --- /dev/null +++ b/src/tools/rustfmt/tests/source/skip_macro_invocations/path_qualified_invocation_mismatch.rs @@ -0,0 +1,6 @@ +// rustfmt-skip_macro_invocations: ["items"] + +// Should not skip this invocation +self::items!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/source/skip_macro_invocations/path_qualified_match.rs b/src/tools/rustfmt/tests/source/skip_macro_invocations/path_qualified_match.rs new file mode 100644 index 000000000..9398918a9 --- /dev/null +++ b/src/tools/rustfmt/tests/source/skip_macro_invocations/path_qualified_match.rs @@ -0,0 +1,6 @@ +// rustfmt-skip_macro_invocations: ["self::items"] + +// Should skip this invocation +self::items!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/source/skip_macro_invocations/path_qualified_name_mismatch.rs b/src/tools/rustfmt/tests/source/skip_macro_invocations/path_qualified_name_mismatch.rs new file mode 100644 index 000000000..4e3eb542d --- /dev/null +++ b/src/tools/rustfmt/tests/source/skip_macro_invocations/path_qualified_name_mismatch.rs @@ -0,0 +1,6 @@ +// rustfmt-skip_macro_invocations: ["self::items"] + +// Should not skip this invocation +items!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/source/skip_macro_invocations/use_alias_examples.rs b/src/tools/rustfmt/tests/source/skip_macro_invocations/use_alias_examples.rs new file mode 100644 index 000000000..43cb8015d --- /dev/null +++ b/src/tools/rustfmt/tests/source/skip_macro_invocations/use_alias_examples.rs @@ -0,0 +1,32 @@ +// rustfmt-skip_macro_invocations: ["aaa","ccc"] + +// These tests demonstrate a realistic use case with use aliases. +// The use statements should not impact functionality in any way. + +use crate::{aaa, bbb, ddd}; + +// No use alias, invocation in list +// Should skip this invocation +aaa!( + const _: u8 = 0; +); + +// Use alias, invocation in list +// Should skip this invocation +use crate::bbb as ccc; +ccc!( + const _: u8 = 0; +); + +// Use alias, invocation not in list +// Should not skip this invocation +use crate::ddd as eee; +eee!( + const _: u8 = 0; +); + +// No use alias, invocation not in list +// Should not skip this invocation +fff!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/source/tuple.rs b/src/tools/rustfmt/tests/source/tuple.rs index 9a0f979fb..5189a7454 100644 --- a/src/tools/rustfmt/tests/source/tuple.rs +++ b/src/tools/rustfmt/tests/source/tuple.rs @@ -1,4 +1,4 @@ -// Test tuple litterals +// Test tuple literals fn foo() { let a = (a, a, a, a, a); diff --git a/src/tools/rustfmt/tests/source/wrap_comments_should_not_imply_format_doc_comments.rs b/src/tools/rustfmt/tests/source/wrap_comments_should_not_imply_format_doc_comments.rs index 78b3ce146..56064e4a4 100644 --- a/src/tools/rustfmt/tests/source/wrap_comments_should_not_imply_format_doc_comments.rs +++ b/src/tools/rustfmt/tests/source/wrap_comments_should_not_imply_format_doc_comments.rs @@ -11,6 +11,6 @@ /// fn foo() {} -/// A long commment for wrapping +/// A long comment for wrapping /// This is a long long long long long long long long long long long long long long long long long long long long sentence. fn bar() {} diff --git a/src/tools/rustfmt/tests/target/cfg_if/detect/arch/x86.rs b/src/tools/rustfmt/tests/target/cfg_if/detect/arch/x86.rs index 02d5eed1c..47210cae2 100644 --- a/src/tools/rustfmt/tests/target/cfg_if/detect/arch/x86.rs +++ b/src/tools/rustfmt/tests/target/cfg_if/detect/arch/x86.rs @@ -314,7 +314,7 @@ pub enum Feature { tbm, /// POPCNT (Population Count) popcnt, - /// FXSR (Floating-point context fast save and restor) + /// FXSR (Floating-point context fast save and restore) fxsr, /// XSAVE (Save Processor Extended States) xsave, diff --git a/src/tools/rustfmt/tests/target/comments_unicode.rs b/src/tools/rustfmt/tests/target/comments_unicode.rs new file mode 100644 index 000000000..3e1b6b0a2 --- /dev/null +++ b/src/tools/rustfmt/tests/target/comments_unicode.rs @@ -0,0 +1,140 @@ +impl Default for WhitespaceCharacters { + fn default() -> Self { + Self { + space: '·', // U+00B7 + nbsp: '⍽', // U+237D + tab: '→', // U+2192 + newline: '⏎', // U+23CE + } + } +} + +const RAINBOWS: &[&str] = &[ + "rаinЬοѡ", // hue: 0 + "raіnЬοw", // hue: 2 + "rаіɴЬow", // hue: 2 + "raіɴЬoѡ", // hue: 8 + "ʀainЬow", // hue: 8 + "ʀaіɴboѡ", // hue: 8 + "ʀаіnbοw", // hue: 11 + "rainЬoѡ", // hue: 14 + "raіɴbow", // hue: 14 + "rаiɴЬow", // hue: 20 + "raіnЬow", // hue: 26 + "ʀaiɴbοw", // hue: 32 + "raіɴboѡ", // hue: 35 + "rаiɴbow", // hue: 35 + "rаіnbοw", // hue: 38 + "rаinЬow", // hue: 47 + "ʀaіnboѡ", // hue: 47 + "ʀaіnЬoѡ", // hue: 47 + "ʀаіɴbοw", // hue: 53 + "ʀaіnЬοѡ", // hue: 57 + "raiɴЬoѡ", // hue: 68 + "ʀainbοѡ", // hue: 68 + "ʀаinboѡ", // hue: 68 + "ʀаiɴbοw", // hue: 68 + "ʀаіnbow", // hue: 68 + "rаіnЬοѡ", // hue: 69 + "ʀainЬοw", // hue: 71 + "raiɴbow", // hue: 73 + "raіnЬoѡ", // hue: 74 + "rаіɴbοw", // hue: 77 + "raіnЬοѡ", // hue: 81 + "raiɴЬow", // hue: 83 + "ʀainbοw", // hue: 83 + "ʀаinbow", // hue: 83 + "ʀаiɴbοѡ", // hue: 83 + "ʀаіnboѡ", // hue: 83 + "ʀаіɴЬοѡ", // hue: 84 + "rainЬow", // hue: 85 + "ʀаiɴЬοw", // hue: 86 + "ʀаіnbοѡ", // hue: 89 + "ʀаіnЬοw", // hue: 92 + "rаiɴbοw", // hue: 95 + "ʀаіɴbοѡ", // hue: 98 + "ʀаiɴЬοѡ", // hue: 99 + "raіnbοw", // hue: 101 + "ʀаіɴЬοw", // hue: 101 + "ʀaiɴboѡ", // hue: 104 + "ʀаinbοѡ", // hue: 104 + "rаiɴbοѡ", // hue: 107 + "ʀаinЬοw", // hue: 107 + "rаiɴЬοw", // hue: 110 + "rаіnboѡ", // hue: 110 + "rаіnbοѡ", // hue: 113 + "ʀainЬοѡ", // hue: 114 + "rаіnЬοw", // hue: 116 + "ʀaіɴЬow", // hue: 116 + "rаinbοw", // hue: 122 + "ʀаіɴboѡ", // hue: 125 + "rаinbοѡ", // hue: 131 + "rainbow", // hue: 134 + "rаinЬοw", // hue: 134 + "ʀаiɴboѡ", // hue: 140 + "rainЬοѡ", // hue: 141 + "raіɴЬow", // hue: 143 + "ʀainЬoѡ", // hue: 143 + "ʀaіɴbow", // hue: 143 + "ʀainbow", // hue: 148 + "rаіɴboѡ", // hue: 149 + "ʀainboѡ", // hue: 155 + "ʀaіnbow", // hue: 155 + "ʀaіnЬow", // hue: 155 + "raiɴbοw", // hue: 158 + "ʀаiɴЬoѡ", // hue: 158 + "rainbοw", // hue: 160 + "rаinbow", // hue: 160 + "ʀaіɴbοѡ", // hue: 164 + "ʀаiɴbow", // hue: 164 + "ʀаіnЬoѡ", // hue: 164 + "ʀaiɴЬοѡ", // hue: 165 + "rаiɴboѡ", // hue: 167 + "ʀaіɴЬοw", // hue: 167 + "ʀaіɴЬοѡ", // hue: 171 + "raіnboѡ", // hue: 173 + "ʀаіɴЬoѡ", // hue: 173 + "rаіɴbοѡ", // hue: 176 + "ʀаinЬow", // hue: 176 + "rаiɴЬοѡ", // hue: 177 + "rаіɴЬοw", // hue: 179 + "ʀаinЬoѡ", // hue: 179 + "ʀаіɴbow", // hue: 179 + "rаiɴЬoѡ", // hue: 182 + "raіɴbοѡ", // hue: 188 + "rаіnЬoѡ", // hue: 188 + "raiɴЬοѡ", // hue: 189 + "raіɴЬοw", // hue: 191 + "ʀaіɴbοw", // hue: 191 + "ʀаіnЬow", // hue: 191 + "rainbοѡ", // hue: 194 + "rаinboѡ", // hue: 194 + "rаіnbow", // hue: 194 + "rainЬοw", // hue: 197 + "rаinЬoѡ", // hue: 206 + "rаіɴbow", // hue: 206 + "rаіɴЬοѡ", // hue: 210 + "ʀaiɴЬow", // hue: 212 + "raіɴbοw", // hue: 218 + "rаіnЬow", // hue: 218 + "ʀaiɴbοѡ", // hue: 221 + "ʀaiɴЬοw", // hue: 224 + "ʀaіnbοѡ", // hue: 227 + "raiɴboѡ", // hue: 230 + "ʀaіnbοw", // hue: 230 + "ʀaіnЬοw", // hue: 230 + "ʀаinЬοѡ", // hue: 231 + "rainboѡ", // hue: 232 + "raіnbow", // hue: 232 + "ʀаіɴЬow", // hue: 233 + "ʀaіɴЬoѡ", // hue: 239 + "ʀаіnЬοѡ", // hue: 246 + "raiɴbοѡ", // hue: 248 + "ʀаiɴЬow", // hue: 248 + "raіɴЬοѡ", // hue: 249 + "raiɴЬοw", // hue: 251 + "rаіɴЬoѡ", // hue: 251 + "ʀaiɴbow", // hue: 251 + "ʀаinbοw", // hue: 251 + "raіnbοѡ", // hue: 254 +]; diff --git a/src/tools/rustfmt/tests/target/configs/fn_args_layout/compressed.rs b/src/tools/rustfmt/tests/target/configs/fn_params_layout/compressed.rs index f189446e2..ff32f0f1d 100644 --- a/src/tools/rustfmt/tests/target/configs/fn_args_layout/compressed.rs +++ b/src/tools/rustfmt/tests/target/configs/fn_params_layout/compressed.rs @@ -1,4 +1,4 @@ -// rustfmt-fn_args_layout: Compressed +// rustfmt-fn_params_layout: Compressed // Function arguments density trait Lorem { diff --git a/src/tools/rustfmt/tests/target/configs/fn_args_layout/tall.rs b/src/tools/rustfmt/tests/target/configs/fn_params_layout/tall.rs index 20f308973..25a86799a 100644 --- a/src/tools/rustfmt/tests/target/configs/fn_args_layout/tall.rs +++ b/src/tools/rustfmt/tests/target/configs/fn_params_layout/tall.rs @@ -1,4 +1,4 @@ -// rustfmt-fn_args_layout: Tall +// rustfmt-fn_params_layout: Tall // Function arguments density trait Lorem { diff --git a/src/tools/rustfmt/tests/target/configs/fn_args_layout/vertical.rs b/src/tools/rustfmt/tests/target/configs/fn_params_layout/vertical.rs index 6c695a75d..7a0e42415 100644 --- a/src/tools/rustfmt/tests/target/configs/fn_args_layout/vertical.rs +++ b/src/tools/rustfmt/tests/target/configs/fn_params_layout/vertical.rs @@ -1,4 +1,4 @@ -// rustfmt-fn_args_layout: Vertical +// rustfmt-fn_params_layout: Vertical // Function arguments density trait Lorem { diff --git a/src/tools/rustfmt/tests/target/enum.rs b/src/tools/rustfmt/tests/target/enum.rs index 9a25126b4..70fc8ab37 100644 --- a/src/tools/rustfmt/tests/target/enum.rs +++ b/src/tools/rustfmt/tests/target/enum.rs @@ -43,7 +43,7 @@ enum StructLikeVariants { x: i32, // Test comment // Pre-comment #[Attr50] - y: SomeType, // Aanother Comment + y: SomeType, // Another Comment }, SL { a: A, diff --git a/src/tools/rustfmt/tests/target/fn-custom-7.rs b/src/tools/rustfmt/tests/target/fn-custom-7.rs index 2c20ac5a7..f6a1a90c3 100644 --- a/src/tools/rustfmt/tests/target/fn-custom-7.rs +++ b/src/tools/rustfmt/tests/target/fn-custom-7.rs @@ -1,5 +1,5 @@ // rustfmt-normalize_comments: true -// rustfmt-fn_args_layout: Vertical +// rustfmt-fn_params_layout: Vertical // rustfmt-brace_style: AlwaysNextLine // Case with only one variable. diff --git a/src/tools/rustfmt/tests/target/fn-custom.rs b/src/tools/rustfmt/tests/target/fn-custom.rs index 2eb2a973d..506d9de34 100644 --- a/src/tools/rustfmt/tests/target/fn-custom.rs +++ b/src/tools/rustfmt/tests/target/fn-custom.rs @@ -1,4 +1,4 @@ -// rustfmt-fn_args_layout: Compressed +// rustfmt-fn_params_layout: Compressed // Test some of the ways function signatures can be customised. // Test compressed layout of args. diff --git a/src/tools/rustfmt/tests/target/fn_args_layout-vertical.rs b/src/tools/rustfmt/tests/target/fn_args_layout-vertical.rs index da0ac981d..bfeca15c9 100644 --- a/src/tools/rustfmt/tests/target/fn_args_layout-vertical.rs +++ b/src/tools/rustfmt/tests/target/fn_args_layout-vertical.rs @@ -1,4 +1,4 @@ -// rustfmt-fn_args_layout: Vertical +// rustfmt-fn_params_layout: Vertical // Empty list should stay on one line. fn do_bar() -> u8 { diff --git a/src/tools/rustfmt/tests/target/issue-2534/format_macro_matchers_false.rs b/src/tools/rustfmt/tests/target/issue-2534/format_macro_matchers_false.rs new file mode 100644 index 000000000..2038ed7f1 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2534/format_macro_matchers_false.rs @@ -0,0 +1,6 @@ +// rustfmt-format_macro_matchers: false + +macro_rules! foo { + ($a:ident : $b:ty) => {}; + ($a:ident $b:ident $c:ident) => {}; +} diff --git a/src/tools/rustfmt/tests/target/issue-2534/format_macro_matchers_true.rs b/src/tools/rustfmt/tests/target/issue-2534/format_macro_matchers_true.rs new file mode 100644 index 000000000..01d939add --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-2534/format_macro_matchers_true.rs @@ -0,0 +1,6 @@ +// rustfmt-format_macro_matchers: true + +macro_rules! foo { + ($a:ident : $b:ty) => {}; + ($a:ident $b:ident $c:ident) => {}; +} diff --git a/src/tools/rustfmt/tests/target/issue-3987/format_macro_bodies_false.rs b/src/tools/rustfmt/tests/target/issue-3987/format_macro_bodies_false.rs new file mode 100644 index 000000000..1352b762e --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3987/format_macro_bodies_false.rs @@ -0,0 +1,26 @@ +// rustfmt-format_macro_bodies: false + +// with comments +macro_rules! macros { + () => {{ + Struct { + field: ( + 42 + //comment 1 + 42 + //comment 2 + ), + }; + }}; +} + +// without comments +macro_rules! macros { + () => {{ + Struct { + field: ( + 42 + + 42 + ), + }; + }}; +} diff --git a/src/tools/rustfmt/tests/target/issue-3987/format_macro_bodies_true.rs b/src/tools/rustfmt/tests/target/issue-3987/format_macro_bodies_true.rs new file mode 100644 index 000000000..88d57159c --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-3987/format_macro_bodies_true.rs @@ -0,0 +1,21 @@ +// rustfmt-format_macro_bodies: true + +// with comments +macro_rules! macros { + () => {{ + Struct { + field: ( + 42 + //comment 1 + 42 + //comment 2 + ), + }; + }}; +} + +// without comments +macro_rules! macros { + () => {{ + Struct { field: (42 + 42) }; + }}; +} diff --git a/src/tools/rustfmt/tests/target/issue-4643.rs b/src/tools/rustfmt/tests/target/issue-4643.rs new file mode 100644 index 000000000..ef99e4db3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4643.rs @@ -0,0 +1,19 @@ +// output doesn't get corrupted when using comments within generic type parameters of a trait + +pub trait Something< + A, + // some comment + B, + C, +> +{ + fn a(&self, x: A) -> i32; + fn b(&self, x: B) -> i32; + fn c(&self, x: C) -> i32; +} + +pub trait SomethingElse<A, /* some comment */ B, C> { + fn a(&self, x: A) -> i32; + fn b(&self, x: B) -> i32; + fn c(&self, x: C) -> i32; +} diff --git a/src/tools/rustfmt/tests/target/issue-4689/one.rs b/src/tools/rustfmt/tests/target/issue-4689/one.rs new file mode 100644 index 000000000..7735e34f3 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4689/one.rs @@ -0,0 +1,150 @@ +// rustfmt-version: One + +// Based on the issue description +pub trait PrettyPrinter<'tcx>: + Printer< + 'tcx, + Error = fmt::Error, + Path = Self, + Region = Self, + Type = Self, + DynExistential = Self, + Const = Self, +> +{ + // +} +pub trait PrettyPrinter<'tcx>: + Printer< + 'tcx, + Error = fmt::Error, + Path = Self, + Region = Self, + Type = Self, + DynExistential = Self, + Const = Self, + > + fmt::Write +{ + // +} +pub trait PrettyPrinter<'tcx>: + Printer< + 'tcx, + Error = fmt::Error, + Path = Self, + Region = Self, + Type = Self, + DynExistential = Self, + Const = Self, + > + fmt::Write1 + + fmt::Write2 +{ + // +} +pub trait PrettyPrinter<'tcx>: + fmt::Write + + Printer< + 'tcx, + Error = fmt::Error, + Path = Self, + Region = Self, + Type = Self, + DynExistential = Self, + Const = Self, + > +{ + // +} +pub trait PrettyPrinter<'tcx>: + fmt::Write + + Printer1< + 'tcx, + Error = fmt::Error, + Path = Self, + Region = Self, + Type = Self, + DynExistential = Self, + Const = Self, + > + Printer2< + 'tcx, + Error = fmt::Error, + Path = Self, + Region = Self, + Type = Self, + DynExistential = Self, + Const = Self, + > +{ + // +} + +// Some test cases to ensure other cases formatting were not changed +fn f() -> Box< + FnMut() -> Thing< + WithType = LongItemName, + Error = LONGLONGLONGLONGLONGONGEvenLongerErrorNameLongerLonger, + >, +> { +} +fn f() -> Box< + FnMut() -> Thing< + WithType = LongItemName, + Error = LONGLONGLONGLONGLONGONGEvenLongerErrorNameLongerLonger, + > + fmt::Write1 + + fmt::Write2, +> { +} + +fn foo<F>(foo2: F) +where + F: Fn( + // this comment is deleted + ), +{ +} +fn foo<F>(foo2: F) +where + F: Fn( + // this comment is deleted + ) + fmt::Write, +{ +} + +fn elaborate_bounds<F>(mut mk_cand: F) +where + F: FnMut( + &mut ProbeContext, + ty::PolyTraitRefffffffffffffffffffffffffffffffff, + tyyyyyyyyyyyyyyyyyyyyy::AssociatedItem, + ), +{ +} +fn elaborate_bounds<F>(mut mk_cand: F) +where + F: FnMut( + &mut ProbeContext, + ty::PolyTraitRefffffffffffffffffffffffffffffffff, + tyyyyyyyyyyyyyyyyyyyyy::AssociatedItem, + ) + fmt::Write, +{ +} + +fn build_sorted_static_get_entry_names( + mut entries: entryyyyyyyy, +) -> (impl Fn( + AlphabeticalTraversal, + Seconddddddddddddddddddddddddddddddddddd, +) -> Parammmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm + + Sendddddddddddddddddddddddddddddddddddddddddddd) { +} + +pub trait SomeTrait: + Cloneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + + Eqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq +{ +} + +trait B = where + for<'b> &'b Self: Send + + Cloneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + + Copyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy; diff --git a/src/tools/rustfmt/tests/target/issue-4689/two.rs b/src/tools/rustfmt/tests/target/issue-4689/two.rs new file mode 100644 index 000000000..e3b5cd228 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-4689/two.rs @@ -0,0 +1,152 @@ +// rustfmt-version: Two + +// Based on the issue description +pub trait PrettyPrinter<'tcx>: + Printer< + 'tcx, + Error = fmt::Error, + Path = Self, + Region = Self, + Type = Self, + DynExistential = Self, + Const = Self, + > +{ + // +} +pub trait PrettyPrinter<'tcx>: + Printer< + 'tcx, + Error = fmt::Error, + Path = Self, + Region = Self, + Type = Self, + DynExistential = Self, + Const = Self, + > + fmt::Write +{ + // +} +pub trait PrettyPrinter<'tcx>: + Printer< + 'tcx, + Error = fmt::Error, + Path = Self, + Region = Self, + Type = Self, + DynExistential = Self, + Const = Self, + > + fmt::Write1 + + fmt::Write2 +{ + // +} +pub trait PrettyPrinter<'tcx>: + fmt::Write + + Printer< + 'tcx, + Error = fmt::Error, + Path = Self, + Region = Self, + Type = Self, + DynExistential = Self, + Const = Self, + > +{ + // +} +pub trait PrettyPrinter<'tcx>: + fmt::Write + + Printer1< + 'tcx, + Error = fmt::Error, + Path = Self, + Region = Self, + Type = Self, + DynExistential = Self, + Const = Self, + > + Printer2< + 'tcx, + Error = fmt::Error, + Path = Self, + Region = Self, + Type = Self, + DynExistential = Self, + Const = Self, + > +{ + // +} + +// Some test cases to ensure other cases formatting were not changed +fn f() -> Box< + FnMut() -> Thing< + WithType = LongItemName, + Error = LONGLONGLONGLONGLONGONGEvenLongerErrorNameLongerLonger, + >, +> { +} +fn f() -> Box< + FnMut() -> Thing< + WithType = LongItemName, + Error = LONGLONGLONGLONGLONGONGEvenLongerErrorNameLongerLonger, + > + fmt::Write1 + + fmt::Write2, +> { +} + +fn foo<F>(foo2: F) +where + F: Fn( + // this comment is deleted + ), +{ +} +fn foo<F>(foo2: F) +where + F: Fn( + // this comment is deleted + ) + fmt::Write, +{ +} + +fn elaborate_bounds<F>(mut mk_cand: F) +where + F: FnMut( + &mut ProbeContext, + ty::PolyTraitRefffffffffffffffffffffffffffffffff, + tyyyyyyyyyyyyyyyyyyyyy::AssociatedItem, + ), +{ +} +fn elaborate_bounds<F>(mut mk_cand: F) +where + F: FnMut( + &mut ProbeContext, + ty::PolyTraitRefffffffffffffffffffffffffffffffff, + tyyyyyyyyyyyyyyyyyyyyy::AssociatedItem, + ) + fmt::Write, +{ +} + +fn build_sorted_static_get_entry_names( + mut entries: entryyyyyyyy, +) -> ( + impl Fn( + AlphabeticalTraversal, + Seconddddddddddddddddddddddddddddddddddd, + ) -> Parammmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm + + Sendddddddddddddddddddddddddddddddddddddddddddd +) { +} + +pub trait SomeTrait: + Cloneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + + Eqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq +{ +} + +trait B = where + for<'b> &'b Self: Send + + Cloneeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + + Copyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy; diff --git a/src/tools/rustfmt/tests/target/issue-4791/issue_4928.rs b/src/tools/rustfmt/tests/target/issue-4791/issue_4928.rs index 588656b53..29f6bda90 100644 --- a/src/tools/rustfmt/tests/target/issue-4791/issue_4928.rs +++ b/src/tools/rustfmt/tests/target/issue-4791/issue_4928.rs @@ -1,7 +1,7 @@ // rustfmt-brace_style: SameLineWhere // rustfmt-comment_width: 100 // rustfmt-edition: 2018 -// rustfmt-fn_args_layout: Compressed +// rustfmt-fn_params_layout: Compressed // rustfmt-hard_tabs: false // rustfmt-match_block_trailing_comma: true // rustfmt-max_width: 100 diff --git a/src/tools/rustfmt/tests/target/issue-5358.rs b/src/tools/rustfmt/tests/target/issue-5358.rs new file mode 100644 index 000000000..d4bf4909a --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue-5358.rs @@ -0,0 +1,4 @@ +// Test /* comment */ inside trait generics does not get duplicated. +trait Test</* comment */ T> {} + +trait TestTwo</* comment */ T, /* comment */ V> {} diff --git a/src/tools/rustfmt/tests/target/issue_1306.rs b/src/tools/rustfmt/tests/target/issue_1306.rs new file mode 100644 index 000000000..6bb514cdf --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_1306.rs @@ -0,0 +1,33 @@ +// rustfmt-max_width: 160 +// rustfmt-fn_call_width: 96 +// rustfmt-fn_args_layout: Compressed +// rustfmt-trailing_comma: Always +// rustfmt-wrap_comments: true + +fn foo() { + for elem in try!(gen_epub_book::ops::parse_descriptor_file( + &mut try!(File::open(&opts.source_file.1).map_err(|_| { + gen_epub_book::Error::Io { + desc: "input file", + op: "open", + more: None, + } + })), + "input file" + )) { + println!("{}", elem); + } +} + +fn write_content() { + io::copy( + try!(File::open(in_f).map_err(|_| { + Error::Io { + desc: "Content", + op: "open", + more: None, + } + })), + w, + ); +} diff --git a/src/tools/rustfmt/tests/target/issue_3033.rs b/src/tools/rustfmt/tests/target/issue_3033.rs new file mode 100644 index 000000000..e12249a6d --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_3033.rs @@ -0,0 +1,2 @@ +use dom::bindings::codegen::Bindings::BluetoothRemoteGATTServerBinding::BluetoothRemoteGATTServerBinding:: + BluetoothRemoteGATTServerMethods; diff --git a/src/tools/rustfmt/tests/target/issue_3245.rs b/src/tools/rustfmt/tests/target/issue_3245.rs new file mode 100644 index 000000000..8f442f118 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_3245.rs @@ -0,0 +1,4 @@ +fn main() { + let x = 1; + let y = 3; +} diff --git a/src/tools/rustfmt/tests/target/issue_3561.rs b/src/tools/rustfmt/tests/target/issue_3561.rs new file mode 100644 index 000000000..846a14d86 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_3561.rs @@ -0,0 +1,7 @@ +fn main() { + 7 +} + +fn main() { + 7 +} diff --git a/src/tools/rustfmt/tests/target/issue_4350.rs b/src/tools/rustfmt/tests/target/issue_4350.rs new file mode 100644 index 000000000..a94c5c321 --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_4350.rs @@ -0,0 +1,13 @@ +//rustfmt-format_macro_bodies: true + +macro_rules! mto_text_left { + ($buf:ident, $n:ident, $pos:ident, $state:ident) => {{ + let cursor = loop { + state = match iter.next() { + None if $pos == DP::Start => break last_char_idx($buf), + None /*some comment */ => break 0, + }; + }; + Ok(saturate_cursor($buf, cursor)) + }}; +} diff --git a/src/tools/rustfmt/tests/target/issue_5668.rs b/src/tools/rustfmt/tests/target/issue_5668.rs new file mode 100644 index 000000000..bbd9a530b --- /dev/null +++ b/src/tools/rustfmt/tests/target/issue_5668.rs @@ -0,0 +1,8 @@ +type Foo = impl Send; +struct Struct< + const C: usize = { + let _: Foo = (); + //~^ ERROR: mismatched types + 0 + }, +>; diff --git a/src/tools/rustfmt/tests/target/skip_macro_invocations/all.rs b/src/tools/rustfmt/tests/target/skip_macro_invocations/all.rs new file mode 100644 index 000000000..d0437ee10 --- /dev/null +++ b/src/tools/rustfmt/tests/target/skip_macro_invocations/all.rs @@ -0,0 +1,11 @@ +// rustfmt-skip_macro_invocations: ["*"] + +// Should skip this invocation +items!( + const _: u8 = 0; +); + +// Should skip this invocation +renamed_items!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/target/skip_macro_invocations/all_and_name.rs b/src/tools/rustfmt/tests/target/skip_macro_invocations/all_and_name.rs new file mode 100644 index 000000000..1f6722344 --- /dev/null +++ b/src/tools/rustfmt/tests/target/skip_macro_invocations/all_and_name.rs @@ -0,0 +1,11 @@ +// rustfmt-skip_macro_invocations: ["*","items"] + +// Should skip this invocation +items!( + const _: u8 = 0; +); + +// Should also skip this invocation, as the wildcard covers it +renamed_items!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/target/skip_macro_invocations/empty.rs b/src/tools/rustfmt/tests/target/skip_macro_invocations/empty.rs new file mode 100644 index 000000000..4a398cc59 --- /dev/null +++ b/src/tools/rustfmt/tests/target/skip_macro_invocations/empty.rs @@ -0,0 +1,11 @@ +// rustfmt-skip_macro_invocations: [] + +// Should not skip this invocation +items!( + const _: u8 = 0; +); + +// Should not skip this invocation +renamed_items!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/target/skip_macro_invocations/name.rs b/src/tools/rustfmt/tests/target/skip_macro_invocations/name.rs new file mode 100644 index 000000000..c4d577269 --- /dev/null +++ b/src/tools/rustfmt/tests/target/skip_macro_invocations/name.rs @@ -0,0 +1,11 @@ +// rustfmt-skip_macro_invocations: ["items"] + +// Should skip this invocation +items!( + const _: u8 = 0; +); + +// Should not skip this invocation +renamed_items!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/target/skip_macro_invocations/name_unknown.rs b/src/tools/rustfmt/tests/target/skip_macro_invocations/name_unknown.rs new file mode 100644 index 000000000..7ab144039 --- /dev/null +++ b/src/tools/rustfmt/tests/target/skip_macro_invocations/name_unknown.rs @@ -0,0 +1,6 @@ +// rustfmt-skip_macro_invocations: ["unknown"] + +// Should not skip this invocation +items!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/target/skip_macro_invocations/names.rs b/src/tools/rustfmt/tests/target/skip_macro_invocations/names.rs new file mode 100644 index 000000000..c6b41ff93 --- /dev/null +++ b/src/tools/rustfmt/tests/target/skip_macro_invocations/names.rs @@ -0,0 +1,16 @@ +// rustfmt-skip_macro_invocations: ["foo","bar"] + +// Should skip this invocation +foo!( + const _: u8 = 0; +); + +// Should skip this invocation +bar!( + const _: u8 = 0; +); + +// Should not skip this invocation +baz!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/target/skip_macro_invocations/path_qualified_invocation_mismatch.rs b/src/tools/rustfmt/tests/target/skip_macro_invocations/path_qualified_invocation_mismatch.rs new file mode 100644 index 000000000..6e372c726 --- /dev/null +++ b/src/tools/rustfmt/tests/target/skip_macro_invocations/path_qualified_invocation_mismatch.rs @@ -0,0 +1,6 @@ +// rustfmt-skip_macro_invocations: ["items"] + +// Should not skip this invocation +self::items!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/target/skip_macro_invocations/path_qualified_match.rs b/src/tools/rustfmt/tests/target/skip_macro_invocations/path_qualified_match.rs new file mode 100644 index 000000000..9398918a9 --- /dev/null +++ b/src/tools/rustfmt/tests/target/skip_macro_invocations/path_qualified_match.rs @@ -0,0 +1,6 @@ +// rustfmt-skip_macro_invocations: ["self::items"] + +// Should skip this invocation +self::items!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/target/skip_macro_invocations/path_qualified_name_mismatch.rs b/src/tools/rustfmt/tests/target/skip_macro_invocations/path_qualified_name_mismatch.rs new file mode 100644 index 000000000..aa57a2a65 --- /dev/null +++ b/src/tools/rustfmt/tests/target/skip_macro_invocations/path_qualified_name_mismatch.rs @@ -0,0 +1,6 @@ +// rustfmt-skip_macro_invocations: ["self::items"] + +// Should not skip this invocation +items!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/target/skip_macro_invocations/use_alias_examples.rs b/src/tools/rustfmt/tests/target/skip_macro_invocations/use_alias_examples.rs new file mode 100644 index 000000000..799dd8c08 --- /dev/null +++ b/src/tools/rustfmt/tests/target/skip_macro_invocations/use_alias_examples.rs @@ -0,0 +1,32 @@ +// rustfmt-skip_macro_invocations: ["aaa","ccc"] + +// These tests demonstrate a realistic use case with use aliases. +// The use statements should not impact functionality in any way. + +use crate::{aaa, bbb, ddd}; + +// No use alias, invocation in list +// Should skip this invocation +aaa!( + const _: u8 = 0; +); + +// Use alias, invocation in list +// Should skip this invocation +use crate::bbb as ccc; +ccc!( + const _: u8 = 0; +); + +// Use alias, invocation not in list +// Should not skip this invocation +use crate::ddd as eee; +eee!( + const _: u8 = 0; +); + +// No use alias, invocation not in list +// Should not skip this invocation +fff!( + const _: u8 = 0; +); diff --git a/src/tools/rustfmt/tests/target/tuple.rs b/src/tools/rustfmt/tests/target/tuple.rs index 68bb2f3bc..24fcf8cfd 100644 --- a/src/tools/rustfmt/tests/target/tuple.rs +++ b/src/tools/rustfmt/tests/target/tuple.rs @@ -1,4 +1,4 @@ -// Test tuple litterals +// Test tuple literals fn foo() { let a = (a, a, a, a, a); diff --git a/src/tools/rustfmt/tests/target/wrap_comments_should_not_imply_format_doc_comments.rs b/src/tools/rustfmt/tests/target/wrap_comments_should_not_imply_format_doc_comments.rs index d61d4d7c2..6ccecc7e0 100644 --- a/src/tools/rustfmt/tests/target/wrap_comments_should_not_imply_format_doc_comments.rs +++ b/src/tools/rustfmt/tests/target/wrap_comments_should_not_imply_format_doc_comments.rs @@ -10,7 +10,7 @@ /// ``` fn foo() {} -/// A long commment for wrapping +/// A long comment for wrapping /// This is a long long long long long long long long long long long long long /// long long long long long long long sentence. fn bar() {} diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs index bc2edf634..bddfdcfaf 100644 --- a/src/tools/tidy/src/deps.rs +++ b/src/tools/tidy/src/deps.rs @@ -59,7 +59,6 @@ const EXCEPTIONS_CRANELIFT: &[(&str, &str)] = &[ ("cranelift-codegen", "Apache-2.0 WITH LLVM-exception"), ("cranelift-codegen-meta", "Apache-2.0 WITH LLVM-exception"), ("cranelift-codegen-shared", "Apache-2.0 WITH LLVM-exception"), - ("cranelift-egraph", "Apache-2.0 WITH LLVM-exception"), ("cranelift-entity", "Apache-2.0 WITH LLVM-exception"), ("cranelift-frontend", "Apache-2.0 WITH LLVM-exception"), ("cranelift-isle", "Apache-2.0 WITH LLVM-exception"), @@ -121,6 +120,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[ "dissimilar", "dlmalloc", "either", + "elsa", "ena", "expect-test", "fallible-iterator", // dependency of `thorin` @@ -179,6 +179,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[ "ppv-lite86", "proc-macro-hack", "proc-macro2", + "pulldown-cmark", "psm", "punycode", "quote", @@ -192,8 +193,6 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[ "regex-automata", "regex-syntax", "remove_dir_all", - "rls-data", - "rls-span", "rustc-demangle", "rustc-hash", "rustc-rayon", @@ -247,6 +246,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[ "unic-langid-macros", "unic-langid-macros-impl", "unic-ucd-version", + "unicase", "unicode-ident", "unicode-normalization", "unicode-script", @@ -286,7 +286,6 @@ const PERMITTED_CRANELIFT_DEPENDENCIES: &[&str] = &[ "cranelift-codegen", "cranelift-codegen-meta", "cranelift-codegen-shared", - "cranelift-egraph", "cranelift-entity", "cranelift-frontend", "cranelift-isle", @@ -321,10 +320,12 @@ const PERMITTED_CRANELIFT_DEPENDENCIES: &[&str] = &[ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", "windows-sys", + "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", + "windows_x86_64_gnullvm", "windows_x86_64_msvc", ]; diff --git a/src/tools/tidy/src/error_codes.rs b/src/tools/tidy/src/error_codes.rs index 5b84b51a0..c60caa0d4 100644 --- a/src/tools/tidy/src/error_codes.rs +++ b/src/tools/tidy/src/error_codes.rs @@ -31,7 +31,7 @@ const IGNORE_DOCTEST_CHECK: &[&str] = &["E0464", "E0570", "E0601", "E0602", "E06 // Error codes that don't yet have a UI test. This list will eventually be removed. const IGNORE_UI_TEST_CHECK: &[&str] = - &["E0461", "E0465", "E0476", "E0514", "E0523", "E0554", "E0640", "E0717", "E0729", "E0789"]; + &["E0461", "E0465", "E0514", "E0554", "E0640", "E0717", "E0729"]; macro_rules! verbose_print { ($verbose:expr, $($fmt:tt)*) => { @@ -45,7 +45,7 @@ pub fn check(root_path: &Path, search_paths: &[&Path], verbose: bool, bad: &mut let mut errors = Vec::new(); // Stage 1: create list - let error_codes = extract_error_codes(root_path, &mut errors, verbose); + let error_codes = extract_error_codes(root_path, &mut errors); println!("Found {} error codes", error_codes.len()); println!("Highest error code: `{}`", error_codes.iter().max().unwrap()); @@ -65,18 +65,17 @@ pub fn check(root_path: &Path, search_paths: &[&Path], verbose: bool, bad: &mut } /// Stage 1: Parses a list of error codes from `error_codes.rs`. -fn extract_error_codes(root_path: &Path, errors: &mut Vec<String>, verbose: bool) -> Vec<String> { +fn extract_error_codes(root_path: &Path, errors: &mut Vec<String>) -> Vec<String> { let path = root_path.join(Path::new(ERROR_CODES_PATH)); let file = fs::read_to_string(&path).unwrap_or_else(|e| panic!("failed to read `{path:?}`: {e}")); let mut error_codes = Vec::new(); - let mut reached_undocumented_codes = false; for line in file.lines() { let line = line.trim(); - if !reached_undocumented_codes && line.starts_with('E') { + if line.starts_with('E') { let split_line = line.split_once(':'); // Extract the error code from the line, emitting a fatal error if it is not in a correct format. @@ -111,23 +110,6 @@ fn extract_error_codes(root_path: &Path, errors: &mut Vec<String>, verbose: bool } error_codes.push(err_code); - } else if reached_undocumented_codes && line.starts_with('E') { - let err_code = match line.split_once(',') { - None => line, - Some((err_code, _)) => err_code, - } - .to_string(); - - verbose_print!(verbose, "warning: Error code `{}` is undocumented.", err_code); - - if error_codes.contains(&err_code) { - errors.push(format!("Found duplicate error code: `{}`", err_code)); - } - - error_codes.push(err_code); - } else if line == ";" { - // Once we reach the undocumented error codes, adapt to different syntax. - reached_undocumented_codes = true; } } diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs index 806e84025..409f75631 100644 --- a/src/tools/tidy/src/ui_tests.rs +++ b/src/tools/tidy/src/ui_tests.rs @@ -9,8 +9,8 @@ use std::path::Path; const ENTRY_LIMIT: usize = 1000; // FIXME: The following limits should be reduced eventually. -const ROOT_ENTRY_LIMIT: usize = 939; -const ISSUES_ENTRY_LIMIT: usize = 1998; +const ROOT_ENTRY_LIMIT: usize = 940; +const ISSUES_ENTRY_LIMIT: usize = 1978; fn check_entries(path: &Path, bad: &mut bool) { for dir in Walk::new(&path.join("ui")) { diff --git a/src/tools/x/Cargo.toml b/src/tools/x/Cargo.toml index 315027279..84a42ca36 100644 --- a/src/tools/x/Cargo.toml +++ b/src/tools/x/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "x" -version = "0.1.0" +version = "0.1.1" description = "Run x.py slightly more conveniently" edition = "2021" publish = false diff --git a/src/tools/x/src/main.rs b/src/tools/x/src/main.rs index 01f718785..5da8a2888 100644 --- a/src/tools/x/src/main.rs +++ b/src/tools/x/src/main.rs @@ -9,11 +9,47 @@ //! We also don't use `pwsh` on Windows, because it is not installed by default; use std::{ - env, io, + env::{self, consts::EXE_EXTENSION}, + io, path::Path, process::{self, Command, ExitStatus}, }; +const PYTHON: &str = "python"; +const PYTHON2: &str = "python2"; +const PYTHON3: &str = "python3"; + +fn python() -> &'static str { + let val = match env::var_os("PATH") { + Some(val) => val, + None => return PYTHON, + }; + + let mut python2 = false; + let mut python3 = false; + + for dir in env::split_paths(&val) { + // `python` should always take precedence over python2 / python3 if it exists + if dir.join(PYTHON).with_extension(EXE_EXTENSION).exists() { + return PYTHON; + } + + python2 |= dir.join(PYTHON2).with_extension(EXE_EXTENSION).exists(); + python3 |= dir.join(PYTHON3).with_extension(EXE_EXTENSION).exists(); + } + + // try 3 before 2 + if python3 { + PYTHON3 + } else if python2 { + PYTHON2 + } else { + // Python was not found on path, so exit + eprintln!("Unable to find python in your PATH. Please check it is installed."); + process::exit(1); + } +} + #[cfg(windows)] fn x_command(dir: &Path) -> Command { let mut cmd = Command::new("powershell.exe"); @@ -51,6 +87,17 @@ fn exec_or_status(command: &mut Command) -> io::Result<ExitStatus> { command.status() } +fn handle_result(result: io::Result<ExitStatus>, cmd: Command) { + match result { + Err(error) => { + eprintln!("Failed to invoke `{:?}`: {}", cmd, error); + } + Ok(status) => { + process::exit(status.code().unwrap_or(1)); + } + } +} + fn main() { match env::args().skip(1).next().as_deref() { Some("--wrapper-version") => { @@ -70,22 +117,19 @@ fn main() { for dir in current.ancestors() { let candidate = dir.join("x.py"); - if candidate.exists() { - let mut cmd = x_command(dir); - - cmd.args(env::args().skip(1)).current_dir(dir); - - let result = exec_or_status(&mut cmd); - - match result { - Err(error) => { - eprintln!("Failed to invoke `{:?}`: {}", cmd, error); - } - Ok(status) => { - process::exit(status.code().unwrap_or(1)); - } + let shell_script_candidate = dir.join("x"); + let mut cmd: Command; + if shell_script_candidate.exists() { + cmd = x_command(dir); + cmd.args(env::args().skip(1)).current_dir(dir); + } else { + // For older checkouts that do not have the x shell script, default to python + cmd = Command::new(python()); + cmd.arg(&candidate).args(env::args().skip(1)).current_dir(dir); } + let result = exec_or_status(&mut cmd); + handle_result(result, cmd); } } |