diff --git a/src/tools/rust-analyzer/.codecov.yml b/src/tools/rust-analyzer/.codecov.yml new file mode 100644 index 0000000000000..68eacb7d08cbb --- /dev/null +++ b/src/tools/rust-analyzer/.codecov.yml @@ -0,0 +1,10 @@ +coverage: + range: 40...60 + status: + patch: off + project: + default: + informational: true + +# Don't leave comments on PRs +comment: false diff --git a/src/tools/rust-analyzer/.github/workflows/coverage.yaml b/src/tools/rust-analyzer/.github/workflows/coverage.yaml new file mode 100644 index 0000000000000..bd4edba747ca1 --- /dev/null +++ b/src/tools/rust-analyzer/.github/workflows/coverage.yaml @@ -0,0 +1,44 @@ +name: Coverage + +on: [pull_request, push] + +env: + CARGO_INCREMENTAL: 0 + CARGO_NET_RETRY: 10 + CI: 1 + RUST_BACKTRACE: short + RUSTUP_MAX_RETRIES: 10 + +jobs: + coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + run: | + rustup update --no-self-update nightly + rustup default nightly + rustup component add --toolchain nightly rust-src rustc-dev rustfmt + # We also install a nightly rustfmt, because we use `--file-lines` in + # a test. + rustup toolchain install nightly --profile minimal --component rustfmt + + rustup toolchain install nightly --component llvm-tools-preview + + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: Install nextest + uses: taiki-e/install-action@nextest + + - name: Generate code coverage + run: cargo llvm-cov --workspace --lcov --output-path lcov.info + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + files: lcov.info + fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true diff --git a/src/tools/rust-analyzer/AGENTS.md b/src/tools/rust-analyzer/AGENTS.md new file mode 120000 index 0000000000000..681311eb9cf45 --- /dev/null +++ b/src/tools/rust-analyzer/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/src/tools/rust-analyzer/CLAUDE.md b/src/tools/rust-analyzer/CLAUDE.md new file mode 100644 index 0000000000000..e8f699d92813a --- /dev/null +++ b/src/tools/rust-analyzer/CLAUDE.md @@ -0,0 +1,40 @@ +**Reminder: All AI usage must be disclosed in commit messages, see +CONTRIBUTING.md for more details.** + +## Build Commands + +```bash +cargo build # Build all crates +cargo test # Run all tests +cargo test -p # Run tests for a specific crate (e.g., cargo test -p hir-ty) +cargo lint # Run clippy on all targets +cargo xtask codegen # Run code generation +cargo xtask tidy # Run tidy checks +UPDATE_EXPECT=1 cargo test # Update test expectations (snapshot tests) +RUN_SLOW_TESTS=1 cargo test # Run heavy/slow tests +``` + +## Key Architectural Invariants + +- Typing in a function body never invalidates global derived data +- Parser/syntax tree is built per-file to enable parallel parsing +- The server is stateless (HTTP-like); context must be re-created from request parameters +- Cancellation uses salsa's cancellation mechanism; computations panic with a `Cancelled` payload + +### Code Generation + +Generated code is committed to the repo. Grammar and AST are generated from `ungrammar`. Run `cargo test -p xtask` after adding inline parser tests (`// test test_name` comments). + +## Testing + +Tests are snapshot-based using `expect-test`. Test fixtures use a mini-language: +- `$0` marks cursor position +- `// ^^^^` labels attach to the line above +- `//- minicore: sized, fn` includes parts of minicore (minimal core library) +- `//- /path/to/file.rs crate:name deps:dep1,dep2` declares files/crates + +## Style Notes + +- Use `stdx::never!` and `stdx::always!` instead of `assert!` for recoverable invariants +- Use `T![fn]` macro instead of `SyntaxKind::FN_KW` +- Use keyword name mangling over underscore prefixing for identifiers: `crate` → `krate`, `fn` → `func`, `struct` → `strukt`, `type` → `ty` diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 9db4dd7cb1a3a..f31dda4a107bc 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -2635,7 +2635,7 @@ dependencies = [ [[package]] name = "smol_str" -version = "0.3.5" +version = "0.3.6" dependencies = [ "arbitrary", "borsh", diff --git a/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs b/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs index ccd001df69b41..cb5eed1b8b6cd 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs @@ -234,7 +234,7 @@ impl Visibility { if mod_.krate(db) == krate { Some(Visibility::Module(mod_, exp)) } else { None } } (Visibility::Module(mod_a, expl_a), Visibility::Module(mod_b, expl_b)) => { - if mod_a.krate(db) != mod_b.krate(db) { + if mod_a == mod_b { // Most module visibilities are `pub(self)`, and assuming no errors // this will be the common and thus fast path. return Some(Visibility::Module( 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 ae8eef2bbb5d9..949b22b0adaa6 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 @@ -5,10 +5,7 @@ use std::borrow::Cow; use base_db::AnchoredPath; use cfg::CfgExpr; use either::Either; -use intern::{ - Symbol, - sym, -}; +use intern::{Symbol, sym}; use itertools::Itertools; use mbe::{DelimiterKind, expect_fragment}; use span::{Edition, FileId, Span}; 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 16e7d51e8734f..45b181eff8f0a 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 @@ -658,7 +658,7 @@ impl<'db> InferenceContext<'_, 'db> { } } if let RecordSpread::Expr(expr) = *spread { - self.infer_expr(expr, &Expectation::has_type(ty), ExprIsRead::Yes); + self.infer_expr_coerce_never(expr, &Expectation::has_type(ty), ExprIsRead::Yes); } ty } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/op.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/op.rs index c79c828cd4420..95d63ffb508f6 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/op.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/op.rs @@ -178,9 +178,9 @@ impl<'a, 'db> InferenceContext<'a, 'db> { // trait matching creating lifetime constraints that are too strict. // e.g., adding `&'a T` and `&'b T`, given `&'x T: Add<&'x T>`, will result // in `&'a T <: &'x T` and `&'b T <: &'x T`, instead of `'a = 'b = 'x`. - let lhs_ty = self.infer_expr_no_expect(lhs_expr, ExprIsRead::No); + let lhs_ty = self.infer_expr_no_expect(lhs_expr, ExprIsRead::Yes); let fresh_var = self.table.next_ty_var(); - self.demand_coerce(lhs_expr, lhs_ty, fresh_var, AllowTwoPhase::No, ExprIsRead::No) + self.demand_coerce(lhs_expr, lhs_ty, fresh_var, AllowTwoPhase::No, ExprIsRead::Yes) } }; let lhs_ty = self.table.resolve_vars_with_obligations(lhs_ty); @@ -200,7 +200,7 @@ impl<'a, 'db> InferenceContext<'a, 'db> { // see `NB` above let rhs_ty = - self.infer_expr_coerce(rhs_expr, &Expectation::HasType(rhs_ty_var), ExprIsRead::No); + self.infer_expr_coerce(rhs_expr, &Expectation::HasType(rhs_ty_var), ExprIsRead::Yes); let rhs_ty = self.table.resolve_vars_with_obligations(rhs_ty); let return_ty = match result { @@ -320,7 +320,11 @@ impl<'a, 'db> InferenceContext<'a, 'db> { if let Some((rhs_expr, rhs_ty)) = opt_rhs && rhs_ty.is_ty_var() { - self.infer_expr_coerce(rhs_expr, &Expectation::HasType(rhs_ty), ExprIsRead::No); + self.infer_expr_coerce( + rhs_expr, + &Expectation::HasType(rhs_ty), + ExprIsRead::Yes, + ); } // Construct an obligation `self_ty : Trait` 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 a0945c3bdce66..c49e943437933 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -40,8 +40,7 @@ use rustc_hash::FxHashSet; use rustc_type_ir::{ AliasTyKind, BoundVarIndexKind, ConstKind, DebruijnIndex, ExistentialPredicate, ExistentialProjection, ExistentialTraitRef, FnSig, Interner, OutlivesPredicate, TermKind, - TyKind, - TypeFoldable, TypeVisitableExt, Upcast, UpcastFrom, elaborate, + TyKind, TypeFoldable, TypeVisitableExt, Upcast, UpcastFrom, elaborate, inherent::{Clause as _, GenericArgs as _, IntoKind as _, Region as _, Ty as _}, }; use smallvec::SmallVec; @@ -1681,10 +1680,16 @@ impl SupertraitsInfo { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum TypeParamAssocTypeShorthandError { - AssocTypeNotFound, - AmbiguousAssocType, +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +enum AssocTypeShorthandResolution { + Resolved(StoredEarlyBinder<(TypeAliasId, StoredGenericArgs)>), + Ambiguous { + /// If one resolution belongs to a sub-trait and one to a supertrait, this contains + /// the sub-trait's resolution. This can be `None` if there is no trait inheritance + /// relationship between the resolutions. + sub_trait_resolution: Option>, + }, + NotFound, Cycle, } @@ -1708,7 +1713,7 @@ fn resolve_type_param_assoc_type_shorthand( def: GenericDefId, param: TypeParamId, assoc_name: Name, -) -> Result, TypeParamAssocTypeShorthandError> { +) -> AssocTypeShorthandResolution { let generics = generics(db, def); let resolver = def.resolver(db); let mut ctx = TyLoweringContext::new( @@ -1719,13 +1724,13 @@ fn resolve_type_param_assoc_type_shorthand( LifetimeElisionKind::AnonymousReportError, ); let interner = ctx.interner; - let mut result = None; let param_ty = Ty::new_param( interner, param, generics.type_or_const_param_idx(param.into()).unwrap() as u32, ); + let mut this_trait_resolution = None; if let GenericDefId::TraitId(containing_trait) = param.parent() && param.local_id() == GenericParams::SELF_PARAM_ID_IN_SELF { @@ -1734,10 +1739,11 @@ fn resolve_type_param_assoc_type_shorthand( containing_trait.trait_items(db).associated_type_by_name(&assoc_name) { let args = GenericArgs::identity_for_item(interner, containing_trait.into()); - result = Some(StoredEarlyBinder::bind((assoc_type, args.store()))); + this_trait_resolution = Some(StoredEarlyBinder::bind((assoc_type, args.store()))); } } + let mut supertraits_resolution = None; for maybe_parent_generics in std::iter::successors(Some(&generics), |generics| generics.parent_generics()) { @@ -1783,34 +1789,53 @@ fn resolve_type_param_assoc_type_shorthand( TypeParamId::trait_self(bounded_trait), assoc_name.clone(), ); - let lookup_on_bounded_trait = match lookup_on_bounded_trait { - Ok(it) => it, - Err( - err @ (TypeParamAssocTypeShorthandError::AmbiguousAssocType - | TypeParamAssocTypeShorthandError::Cycle), - ) => return Err(*err), - Err(TypeParamAssocTypeShorthandError::AssocTypeNotFound) => { + let assoc_type_and_args = match &lookup_on_bounded_trait { + AssocTypeShorthandResolution::Resolved(trait_ref) => trait_ref, + AssocTypeShorthandResolution::Ambiguous { + sub_trait_resolution: Some(trait_ref), + } => trait_ref, + AssocTypeShorthandResolution::Ambiguous { sub_trait_resolution: None } => { + return AssocTypeShorthandResolution::Ambiguous { + sub_trait_resolution: this_trait_resolution, + }; + } + AssocTypeShorthandResolution::NotFound => { never!("we checked that the trait defines this assoc type"); continue; } + AssocTypeShorthandResolution::Cycle => return AssocTypeShorthandResolution::Cycle, }; - let (assoc_type, args) = lookup_on_bounded_trait - .get_with(|(assoc_type, args)| (*assoc_type, args.as_ref())) - .skip_binder(); - let args = EarlyBinder::bind(args).instantiate(interner, bounded_trait_ref.args); - let current_result = StoredEarlyBinder::bind((assoc_type, args.store())); - // If we already have a result, this is an ambiguity - unless this is the same result, then we are fine - // (e.g. rustc allows to write the same bound twice without ambiguity). - if let Some(existing_result) = result - && existing_result != current_result - { - return Err(TypeParamAssocTypeShorthandError::AmbiguousAssocType); + if let Some(this_trait_resolution) = this_trait_resolution { + return AssocTypeShorthandResolution::Ambiguous { + sub_trait_resolution: Some(this_trait_resolution), + }; + } else if supertraits_resolution.is_some() { + return AssocTypeShorthandResolution::Ambiguous { sub_trait_resolution: None }; + } else { + let (assoc_type, args) = assoc_type_and_args + .get_with(|(assoc_type, args)| (*assoc_type, args.as_ref())) + .skip_binder(); + let args = EarlyBinder::bind(args).instantiate(interner, bounded_trait_ref.args); + let current_result = StoredEarlyBinder::bind((assoc_type, args.store())); + supertraits_resolution = Some(match lookup_on_bounded_trait { + AssocTypeShorthandResolution::Resolved(_) => { + AssocTypeShorthandResolution::Resolved(current_result) + } + AssocTypeShorthandResolution::Ambiguous { .. } => { + AssocTypeShorthandResolution::Ambiguous { + sub_trait_resolution: Some(current_result), + } + } + AssocTypeShorthandResolution::NotFound + | AssocTypeShorthandResolution::Cycle => unreachable!(), + }); } - result = Some(current_result); } } - result.ok_or(TypeParamAssocTypeShorthandError::AssocTypeNotFound) + supertraits_resolution + .or_else(|| this_trait_resolution.map(AssocTypeShorthandResolution::Resolved)) + .unwrap_or(AssocTypeShorthandResolution::NotFound) } fn resolve_type_param_assoc_type_shorthand_cycle_result( @@ -1819,8 +1844,8 @@ fn resolve_type_param_assoc_type_shorthand_cycle_result( _def: GenericDefId, _param: TypeParamId, _assoc_name: Name, -) -> Result, TypeParamAssocTypeShorthandError> { - Err(TypeParamAssocTypeShorthandError::Cycle) +) -> AssocTypeShorthandResolution { + AssocTypeShorthandResolution::Cycle } #[inline] @@ -2468,7 +2493,7 @@ fn fn_sig_for_struct_constructor( let inputs_and_output = Tys::new_from_iter(DbInterner::new_no_crate(db), params.chain(Some(ret.as_ref()))); StoredEarlyBinder::bind(StoredPolyFnSig::new(Binder::dummy(FnSig { - abi: FnAbi::RustCall, + abi: FnAbi::Rust, c_variadic: false, safety: Safety::Safe, inputs_and_output, @@ -2487,7 +2512,7 @@ fn fn_sig_for_enum_variant_constructor( let inputs_and_output = Tys::new_from_iter(DbInterner::new_no_crate(db), params.chain(Some(ret.as_ref()))); StoredEarlyBinder::bind(StoredPolyFnSig::new(Binder::dummy(FnSig { - abi: FnAbi::RustCall, + abi: FnAbi::Rust, c_variadic: false, safety: Safety::Safe, inputs_and_output, @@ -2569,19 +2594,22 @@ pub(crate) fn associated_ty_item_bounds<'db>( EarlyBinder::bind(BoundExistentialPredicates::new_from_slice(&bounds)) } -pub(crate) fn associated_type_by_name_including_super_traits<'db>( +pub(crate) fn associated_type_by_name_including_super_traits_allow_ambiguity<'db>( db: &'db dyn HirDatabase, trait_ref: TraitRef<'db>, name: Name, ) -> Option<(TypeAliasId, GenericArgs<'db>)> { - let assoc_type = resolve_type_param_assoc_type_shorthand( - db, - trait_ref.def_id.0.into(), - TypeParamId::trait_self(trait_ref.def_id.0), - name.clone(), - ) - .as_ref() - .ok()?; + let (AssocTypeShorthandResolution::Resolved(assoc_type) + | AssocTypeShorthandResolution::Ambiguous { sub_trait_resolution: Some(assoc_type) }) = + resolve_type_param_assoc_type_shorthand( + db, + trait_ref.def_id.0.into(), + TypeParamId::trait_self(trait_ref.def_id.0), + name.clone(), + ) + else { + return None; + }; let (assoc_type, trait_args) = assoc_type .get_with(|(assoc_type, trait_args)| (*assoc_type, trait_args.as_ref())) .skip_binder(); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs index d47d696259d54..79f29d370f5cd 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs @@ -30,7 +30,10 @@ use crate::{ consteval::{unknown_const, unknown_const_as_generic}, db::HirDatabase, generics::{Generics, generics}, - lower::{GenericPredicateSource, LifetimeElisionKind, PathDiagnosticCallbackData}, + lower::{ + AssocTypeShorthandResolution, GenericPredicateSource, LifetimeElisionKind, + PathDiagnosticCallbackData, + }, next_solver::{ Binder, Clause, Const, DbInterner, EarlyBinder, ErrorGuaranteed, GenericArg, GenericArgs, Predicate, ProjectionPredicate, Region, TraitRef, Ty, @@ -38,8 +41,8 @@ use crate::{ }; use super::{ - ImplTraitLoweringMode, TyLoweringContext, associated_type_by_name_including_super_traits, - const_param_ty_query, ty_query, + ImplTraitLoweringMode, TyLoweringContext, + associated_type_by_name_including_super_traits_allow_ambiguity, const_param_ty_query, ty_query, }; type CallbackData<'a> = @@ -482,12 +485,14 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> { let error_ty = || Ty::new_error(self.ctx.interner, ErrorGuaranteed); let (assoc_type, trait_args) = match res { Some(TypeNs::GenericParam(param)) => { - let Ok(assoc_type) = super::resolve_type_param_assoc_type_shorthand( - db, - def, - param, - assoc_name.clone(), - ) else { + let AssocTypeShorthandResolution::Resolved(assoc_type) = + super::resolve_type_param_assoc_type_shorthand( + db, + def, + param, + assoc_name.clone(), + ) + else { return error_ty(); }; assoc_type @@ -500,12 +505,14 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> { }; let impl_trait = impl_trait.instantiate_identity(); // Searching for `Self::Assoc` in `impl Trait for Type` is like searching for `Self::Assoc` in `Trait`. - let Ok(assoc_type) = super::resolve_type_param_assoc_type_shorthand( - db, - impl_trait.def_id.0.into(), - TypeParamId::trait_self(impl_trait.def_id.0), - assoc_name.clone(), - ) else { + let AssocTypeShorthandResolution::Resolved(assoc_type) = + super::resolve_type_param_assoc_type_shorthand( + db, + impl_trait.def_id.0.into(), + TypeParamId::trait_self(impl_trait.def_id.0), + assoc_name.clone(), + ) + else { return error_ty(); }; let (assoc_type, trait_args) = assoc_type @@ -869,7 +876,7 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> { let interner = self.ctx.interner; self.current_or_prev_segment.args_and_bindings.map(|args_and_bindings| { args_and_bindings.bindings.iter().enumerate().flat_map(move |(binding_idx, binding)| { - let found = associated_type_by_name_including_super_traits( + let found = associated_type_by_name_including_super_traits_allow_ambiguity( self.ctx.db, trait_ref, binding.name.clone(), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/abi.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/abi.rs index 80d1ea4aa4d00..1813abab86e7b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/abi.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/abi.rs @@ -62,7 +62,6 @@ impl<'db> rustc_type_ir::inherent::Abi> for FnAbi { } fn is_rust(self) -> bool { - // TODO: rustc does not consider `RustCall` to be true here, but Chalk does - matches!(self, FnAbi::Rust | FnAbi::RustCall) + matches!(self, FnAbi::Rust) } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs index e4e7b4cc38859..993293bb56857 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/never_type.rs @@ -802,6 +802,37 @@ fn foo() { ); } +#[test] +fn binop_rhs_never_place_diverges() { + check_no_mismatches( + r#" +//- minicore: sized, add +fn foo() -> i32 { + unsafe { + let p: *mut ! = 0 as _; + let mut x: i32 = 0; + x += *p; + } +} +"#, + ); +} + +#[test] +fn binop_lhs_never_place_diverges() { + check_no_mismatches( + r#" +//- minicore: sized, add +fn foo() { + unsafe { + let p: *mut ! = 0 as _; + *p += 1; + } +} +"#, + ); +} + #[test] fn never_place_isnt_diverging() { check_infer_with_mismatches( @@ -855,3 +886,18 @@ fn foo() { "#]], ); } + +#[test] +fn never_coercion_in_struct_update_syntax() { + check_no_mismatches( + r#" +struct Struct { + field: i32, +} + +fn example() -> Struct { + Struct { ..loop {} } +} + "#, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs index 658c304aac012..1939db0ef5a76 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs @@ -2795,3 +2795,23 @@ fn foo() { "#]], ); } + +#[test] +fn regression_21742() { + check_no_mismatches( + r#" +pub trait IntoIterator { + type Item; +} + +pub trait Collection: IntoIterator::Item> { + type Item; + fn contains(&self, item: &::Item); +} + +fn contains_0>(points: &S) { + points.contains(&0) +} + "#, + ); +} 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 cdf7b40003b5b..74e9a8dac0e79 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 @@ -2217,6 +2217,40 @@ fn test() { ); } +#[test] +fn tuple_struct_constructor_as_fn_trait() { + check_types( + r#" +//- minicore: fn +struct S(u32, u64); + +fn takes_fn S>(f: F) -> S { f(1, 2) } + +fn test() { + takes_fn(S); + //^^^^^^^^^^^ S +} +"#, + ); +} + +#[test] +fn enum_variant_constructor_as_fn_trait() { + check_types( + r#" +//- minicore: fn +enum E { A(u32) } + +fn takes_fn E>(f: F) -> E { f(1) } + +fn test() { + takes_fn(E::A); + //^^^^^^^^^^^^^^ E +} +"#, + ); +} + #[test] fn fn_item_fn_trait() { check_types( diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 9dbee16dae6b7..11d79e2d7bf85 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -5952,7 +5952,16 @@ impl<'db> Type<'db> { ) -> R { let module = resolver.module(); let interner = DbInterner::new_with(db, module.krate(db)); - let infcx = interner.infer_ctxt().build(TypingMode::PostAnalysis); + // Most IDE operations want to operate in PostAnalysis mode, revealing opaques. This makes + // for a nicer IDE experience. However, method resolution is always done on real code (either + // existing code or code to be inserted), and there using PostAnalysis is dangerous - we may + // suggest invalid methods. So we're using the TypingMode of the body we're in. + let typing_mode = if let Some(body_owner) = resolver.body_owner() { + TypingMode::analysis_in_body(interner, body_owner.into()) + } else { + TypingMode::non_body_analysis() + }; + let infcx = interner.infer_ctxt().build(typing_mode); let unstable_features = MethodResolutionUnstableFeatures::from_def_map(resolver.top_level_def_map()); let environment = param_env_from_resolver(db, resolver); @@ -6149,6 +6158,13 @@ impl<'db> Type<'db> { Some(adt.into()) } + /// Holes in the args can come from lifetime/const params. + pub fn as_adt_with_args(&self) -> Option<(Adt, Vec>>)> { + let (adt, args) = self.ty.as_adt()?; + let args = args.iter().map(|arg| Some(self.derived(arg.ty()?))).collect(); + Some((adt.into(), args)) + } + pub fn as_builtin(&self) -> Option { self.ty.as_builtin().map(|inner| BuiltinType { inner }) } diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index 4bc757da44174..1cf3b98160820 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -1819,6 +1819,28 @@ impl<'db> SemanticsImpl<'db> { self.analyze(try_expr.syntax())?.resolve_try_expr(self.db, try_expr) } + /// The type that the associated `try` block, closure or function expects. + pub fn try_expr_returned_type(&self, try_expr: &ast::TryExpr) -> Option> { + self.ancestors_with_macros(try_expr.syntax().clone()).find_map(|parent| { + if let Some(try_block) = ast::BlockExpr::cast(parent.clone()) + && try_block.try_block_modifier().is_some() + { + Some(self.type_of_expr(&try_block.into())?.original) + } else if let Some(closure) = ast::ClosureExpr::cast(parent.clone()) { + Some( + self.type_of_expr(&closure.into())? + .original + .as_callable(self.db)? + .return_type(), + ) + } else if let Some(function) = ast::Fn::cast(parent) { + Some(self.to_def(&function)?.ret_type(self.db)) + } else { + None + } + }) + } + // This does not resolve the method call to the correct trait impl! // We should probably fix that. pub fn resolve_method_call_as_callable( 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 10c3ff0e4d2be..c0ce057d7798d 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 @@ -44,8 +44,7 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) let arm_list_range = ctx.sema.original_range_opt(match_arm_list.syntax())?; if cursor_at_trivial_match_arm_list(ctx, &match_expr, &match_arm_list).is_none() { - let arm_list_range = ctx.sema.original_range(match_arm_list.syntax()).range; - let cursor_in_range = arm_list_range.contains_range(ctx.selection_trimmed()); + let cursor_in_range = arm_list_range.range.contains_range(ctx.selection_trimmed()); if cursor_in_range { cov_mark::hit!(not_applicable_outside_of_range_right); return None; @@ -348,8 +347,8 @@ fn cursor_at_trivial_match_arm_list( // $0 // } if let Some(last_arm) = match_arm_list.arms().last() { - let last_arm_range = last_arm.syntax().text_range(); - let match_expr_range = match_expr.syntax().text_range(); + let last_arm_range = ctx.sema.original_range_opt(last_arm.syntax())?.range; + let match_expr_range = ctx.sema.original_range_opt(match_expr.syntax())?.range; if last_arm_range.end() <= ctx.offset() && ctx.offset() < match_expr_range.end() { cov_mark::hit!(add_missing_match_arms_end_of_last_arm); return Some(()); @@ -1612,6 +1611,38 @@ fn foo(t: Test) { Test::B => ${2:todo!()}, Test::C => ${3:todo!()},$0 }); +}"#, + ); + + check_assist( + add_missing_match_arms, + r#" +macro_rules! m { ($expr:expr) => {$expr}} +enum Test { + A, + B, + C, +} + +fn foo(t: Test) { + m!(match t { + Test::A => (), + $0}); +}"#, + r#" +macro_rules! m { ($expr:expr) => {$expr}} +enum Test { + A, + B, + C, +} + +fn foo(t: Test) { + m!(match t { + Test::A=>(), + Test::B => ${1:todo!()}, + Test::C => ${2:todo!()},$0 + }); }"#, ); } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_struct_binding.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_struct_binding.rs index bb5d11221087e..4c4cee1d7811f 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_struct_binding.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_struct_binding.rs @@ -1,15 +1,19 @@ -use hir::HasVisibility; +use hir::{HasVisibility, Semantics}; use ide_db::{ - FxHashMap, FxHashSet, + FxHashMap, FxHashSet, RootDatabase, assists::AssistId, defs::Definition, helpers::mod_path_to_ast, search::{FileReference, SearchScope}, }; use itertools::Itertools; -use syntax::ast::{HasName, syntax_factory::SyntaxFactory}; use syntax::syntax_editor::SyntaxEditor; use syntax::{AstNode, Edition, SmolStr, SyntaxNode, ToSmolStr, ast}; +use syntax::{ + SyntaxToken, + ast::{HasName, edit::IndentLevel, syntax_factory::SyntaxFactory}, + syntax_editor::Position, +}; use crate::{ assist_context::{AssistContext, Assists, SourceChangeBuilder}, @@ -44,33 +48,90 @@ use crate::{ // } // ``` pub(crate) fn destructure_struct_binding(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let ident_pat = ctx.find_node_at_offset::()?; - let data = collect_data(ident_pat, ctx)?; + let target = ctx.find_node_at_offset::()?; + let data = collect_data(target, ctx)?; acc.add( AssistId::refactor_rewrite("destructure_struct_binding"), "Destructure struct binding", - data.ident_pat.syntax().text_range(), + data.target.syntax().text_range(), |edit| destructure_struct_binding_impl(ctx, edit, &data), ); Some(()) } +enum Target { + IdentPat(ast::IdentPat), + SelfParam { param: ast::SelfParam, insert_after: SyntaxToken }, +} + +impl Target { + fn ty<'db>(&self, sema: &Semantics<'db, RootDatabase>) -> Option> { + match self { + Target::IdentPat(pat) => sema.type_of_binding_in_pat(pat), + Target::SelfParam { param, .. } => sema.type_of_self(param), + } + } + + fn is_ref(&self) -> bool { + match self { + Target::IdentPat(ident_pat) => ident_pat.ref_token().is_some(), + Target::SelfParam { .. } => false, + } + } + + fn is_mut(&self) -> bool { + match self { + Target::IdentPat(ident_pat) => ident_pat.mut_token().is_some(), + Target::SelfParam { param, .. } => { + param.mut_token().is_some() && param.amp_token().is_none() + } + } + } +} + +impl HasName for Target {} + +impl AstNode for Target { + fn cast(node: SyntaxNode) -> Option { + if ast::IdentPat::can_cast(node.kind()) { + ast::IdentPat::cast(node).map(Self::IdentPat) + } else { + let param = ast::SelfParam::cast(node)?; + let param_list = param.syntax().parent().and_then(ast::ParamList::cast)?; + let block = param_list.syntax().parent()?.children().find_map(ast::BlockExpr::cast)?; + let insert_after = block.stmt_list()?.l_curly_token()?; + Some(Self::SelfParam { param, insert_after }) + } + } + + fn can_cast(kind: syntax::SyntaxKind) -> bool { + ast::IdentPat::can_cast(kind) || ast::SelfParam::can_cast(kind) + } + + fn syntax(&self) -> &SyntaxNode { + match self { + Target::IdentPat(ident_pat) => ident_pat.syntax(), + Target::SelfParam { param, .. } => param.syntax(), + } + } +} + fn destructure_struct_binding_impl( ctx: &AssistContext<'_>, builder: &mut SourceChangeBuilder, data: &StructEditData, ) { let field_names = generate_field_names(ctx, data); - let mut editor = builder.make_editor(data.ident_pat.syntax()); + let mut editor = builder.make_editor(data.target.syntax()); destructure_pat(ctx, &mut editor, data, &field_names); update_usages(ctx, &mut editor, data, &field_names.into_iter().collect()); builder.add_file_edits(ctx.vfs_file_id(), editor); } struct StructEditData { - ident_pat: ast::IdentPat, + target: Target, name: ast::Name, kind: hir::StructKind, struct_def_path: hir::ModPath, @@ -83,11 +144,44 @@ struct StructEditData { edition: Edition, } -fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option { - let ty = ctx.sema.type_of_binding_in_pat(&ident_pat)?; +impl StructEditData { + fn apply_to_destruct( + &self, + new_pat: ast::Pat, + editor: &mut SyntaxEditor, + make: &SyntaxFactory, + ) { + match &self.target { + Target::IdentPat(pat) => { + // If the binding is nested inside a record, we need to wrap the new + // destructured pattern in a non-shorthand record field + if self.need_record_field_name { + let new_pat = + make.record_pat_field(make.name_ref(&self.name.to_string()), new_pat); + editor.replace(pat.syntax(), new_pat.syntax()) + } else { + editor.replace(pat.syntax(), new_pat.syntax()) + } + } + Target::SelfParam { insert_after, .. } => { + let indent = IndentLevel::from_token(insert_after) + 1; + let newline = make.whitespace(&format!("\n{indent}")); + let initializer = make.expr_path(make.ident_path("self")); + let let_stmt = make.let_stmt(new_pat, None, Some(initializer)); + editor.insert_all( + Position::after(insert_after), + vec![newline.into(), let_stmt.syntax().clone().into()], + ); + } + } + } +} + +fn collect_data(target: Target, ctx: &AssistContext<'_>) -> Option { + let ty = target.ty(&ctx.sema)?; let hir::Adt::Struct(struct_type) = ty.strip_references().as_adt()? else { return None }; - let module = ctx.sema.scope(ident_pat.syntax())?.module(); + let module = ctx.sema.scope(target.syntax())?.module(); let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(module.krate(ctx.db()))); let struct_def = hir::ModuleDef::from(struct_type); let kind = struct_type.kind(ctx.db()); @@ -116,15 +210,17 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option ctx.sema.to_def(pat), + Target::SelfParam { param, .. } => ctx.sema.to_def(param), + }; + let usages = def .and_then(|def| { Definition::Local(def) .usages(&ctx.sema) @@ -136,11 +232,11 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option) -> Option, - ident_pat: &ast::IdentPat, + target: &Target, usages: &[FileReference], ) -> Option> { fn last_usage(usages: &[FileReference]) -> Option { @@ -165,7 +261,7 @@ fn get_names_in_scope( // If available, find names visible to the last usage of the binding // else, find names visible to the binding itself let last_usage = last_usage(usages); - let node = last_usage.as_ref().unwrap_or(ident_pat.syntax()); + let node = last_usage.as_ref().unwrap_or(target.syntax()); let scope = ctx.sema.scope(node)?; let mut names = FxHashSet::default(); @@ -183,12 +279,9 @@ fn destructure_pat( data: &StructEditData, field_names: &[(SmolStr, SmolStr)], ) { - let ident_pat = &data.ident_pat; - let name = &data.name; - let struct_path = mod_path_to_ast(&data.struct_def_path, data.edition); - let is_ref = ident_pat.ref_token().is_some(); - let is_mut = ident_pat.mut_token().is_some(); + let is_ref = data.target.is_ref(); + let is_mut = data.target.is_mut(); let make = SyntaxFactory::with_mappings(); let new_pat = match data.kind { @@ -221,16 +314,8 @@ fn destructure_pat( hir::StructKind::Unit => make.path_pat(struct_path), }; - // If the binding is nested inside a record, we need to wrap the new - // destructured pattern in a non-shorthand record field - let destructured_pat = if data.need_record_field_name { - make.record_pat_field(make.name_ref(&name.to_string()), new_pat).syntax().clone() - } else { - new_pat.syntax().clone() - }; - + data.apply_to_destruct(new_pat, editor, &make); editor.add_mappings(make.finish_with_mappings()); - editor.replace(data.ident_pat.syntax(), destructured_pat); } fn generate_field_names(ctx: &AssistContext<'_>, data: &StructEditData) -> Vec<(SmolStr, SmolStr)> { @@ -698,6 +783,84 @@ mod tests { ) } + #[test] + fn mut_self_param() { + check_assist( + destructure_struct_binding, + r#" + struct Foo { bar: i32, baz: i32 } + + impl Foo { + fn foo(mut $0self) { + self.bar = 5; + } + } + "#, + r#" + struct Foo { bar: i32, baz: i32 } + + impl Foo { + fn foo(mut self) { + let Foo { mut bar, mut baz } = self; + bar = 5; + } + } + "#, + ) + } + + #[test] + fn ref_mut_self_param() { + check_assist( + destructure_struct_binding, + r#" + struct Foo { bar: i32, baz: i32 } + + impl Foo { + fn foo(&mut $0self) { + self.bar = 5; + } + } + "#, + r#" + struct Foo { bar: i32, baz: i32 } + + impl Foo { + fn foo(&mut self) { + let Foo { bar, baz } = self; + *bar = 5; + } + } + "#, + ) + } + + #[test] + fn ref_self_param() { + check_assist( + destructure_struct_binding, + r#" + struct Foo { bar: i32, baz: i32 } + + impl Foo { + fn foo(&$0self) -> &i32 { + &self.bar + } + } + "#, + r#" + struct Foo { bar: i32, baz: i32 } + + impl Foo { + fn foo(&self) -> &i32 { + let Foo { bar, baz } = self; + bar + } + } + "#, + ) + } + #[test] fn ref_not_add_parenthesis_and_deref_record() { check_assist( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs index b8dc59f87dee7..d51a3a26a3c7c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/destructure_tuple_binding.rs @@ -164,6 +164,7 @@ enum RefType { Mutable, } struct TupleData { + // FIXME: After removing ted, it may be possible to reuse destructure_struct_binding::Target ident_pat: IdentPat, ref_type: Option, field_names: Vec, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_guard.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_guard.rs index fdc5a0fbda359..b4c347ff36a7c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_guard.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/move_guard.rs @@ -567,7 +567,8 @@ fn main() { match 92 { x => if true && true - && true { + && true + { { false } 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 dcadb5368d525..8ff30fce5b5d4 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 @@ -972,7 +972,8 @@ fn main() { fn main() { if true { match path.strip_prefix(root_path) - .and(x) { + .and(x) + { Ok(rel_path) => { let rel_path = RelativePathBuf::from_path(rel_path) .ok()?; @@ -1012,7 +1013,8 @@ fn main() { fn main() { if true { match path.strip_prefix(root_path) - .and(x) { + .and(x) + { Ok(rel_path) => { Foo { x: 1 @@ -1046,7 +1048,8 @@ fn main() { fn main() { if true { match true - && false { + && false + { true => foo(), false => (), } @@ -1925,7 +1928,8 @@ fn main() { fn main() { if true { if let Ok(rel_path) = path.strip_prefix(root_path) - .and(x) { + .and(x) + { Foo { x: 2 } @@ -1965,7 +1969,8 @@ fn main() { fn main() { if true { if let Ok(rel_path) = path.strip_prefix(root_path) - .and(x) { + .and(x) + { let rel_path = RelativePathBuf::from_path(rel_path) .ok()?; Some((*id, rel_path)) 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 a58592b1365b8..ea53aef40c2e7 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 @@ -16,7 +16,7 @@ use itertools::Itertools; use stdx::never; use syntax::{ SmolStr, - SyntaxKind::{BLOCK_EXPR, EXPR_STMT, STMT_LIST}, + SyntaxKind::{EXPR_STMT, STMT_LIST}, T, TextRange, TextSize, ToSmolStr, ast::{self, AstNode, AstToken}, format_smolstr, match_ast, @@ -52,6 +52,7 @@ pub(crate) fn complete_postfix( _ => return, }; let expr_ctx = &dot_access.ctx; + let receiver_accessor = receiver_accessor(dot_receiver); let receiver_text = get_receiver_text(&ctx.sema, dot_receiver, receiver_is_ambiguous_float_literal); @@ -90,7 +91,7 @@ pub(crate) fn complete_postfix( // 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 - let (dot_receiver_including_refs, prefix) = include_references(dot_receiver); + let (dot_receiver_including_refs, prefix) = include_references(&receiver_accessor); let mut receiver_text = receiver_text; receiver_text.insert_str(0, &prefix); let postfix_snippet = @@ -111,13 +112,8 @@ pub(crate) fn complete_postfix( .add_to(acc, ctx.db); let try_enum = TryEnum::from_ty(&ctx.sema, receiver_ty); - let mut is_in_cond = false; - if let Some(parent) = dot_receiver_including_refs.syntax().parent() - && let Some(second_ancestor) = parent.parent() - { - if let Some(parent_expr) = ast::Expr::cast(parent) { - is_in_cond = is_in_condition(&parent_expr); - } + let is_in_cond = is_in_condition(&dot_receiver_including_refs); + if let Some(parent) = dot_receiver_including_refs.syntax().parent() { let placeholder = suggest_receiver_name(dot_receiver, "0", &ctx.sema); match &try_enum { Some(try_enum) if is_in_cond => match try_enum { @@ -154,13 +150,13 @@ pub(crate) fn complete_postfix( postfix_snippet("let", "let", &format!("let $1 = {receiver_text}")) .add_to(acc, ctx.db); } - _ if matches!(second_ancestor.kind(), STMT_LIST | EXPR_STMT | BLOCK_EXPR) => { + _ if matches!(parent.kind(), STMT_LIST | EXPR_STMT) => { postfix_snippet("let", "let", &format!("let $0 = {receiver_text};")) .add_to(acc, ctx.db); postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};")) .add_to(acc, ctx.db); } - _ if ast::MatchArm::can_cast(second_ancestor.kind()) => { + _ if ast::MatchArm::can_cast(parent.kind()) => { postfix_snippet( "let", "let", @@ -305,7 +301,7 @@ pub(crate) fn complete_postfix( postfix_snippet("const", "const {}", &const_completion_string).add_to(acc, ctx.db); } - if let ast::Expr::Literal(literal) = dot_receiver_including_refs.clone() + if let ast::Expr::Literal(literal) = dot_receiver.clone() && let Some(literal_text) = ast::String::cast(literal.token()) { add_format_like_completions(acc, ctx, &dot_receiver_including_refs, cap, &literal_text); @@ -392,14 +388,22 @@ fn escape_snippet_bits(text: &mut String) { stdx::replace(text, '$', "\\$"); } +fn receiver_accessor(receiver: &ast::Expr) -> ast::Expr { + receiver + .syntax() + .parent() + .and_then(ast::Expr::cast) + .filter(|it| { + matches!( + it, + ast::Expr::FieldExpr(_) | ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) + ) + }) + .unwrap_or_else(|| receiver.clone()) +} + fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) { let mut resulting_element = initial_element.clone(); - - while let Some(field_expr) = resulting_element.syntax().parent().and_then(ast::FieldExpr::cast) - { - resulting_element = ast::Expr::from(field_expr); - } - let mut prefix = String::new(); let mut found_ref_or_deref = false; @@ -898,6 +902,30 @@ fn main() { bar => bar.$0 } } +"#, + expect![[r#" + sn box Box::new(expr) + sn call function(expr) + sn const const {} + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn deref *expr + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn return return expr + sn unsafe unsafe {} + "#]], + ); + check( + r#" +fn main() { + match 2 { + bar => &bar.l$0 + } +} "#, expect![[r#" sn box Box::new(expr) 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 5fef8c44deb12..8e50ef10eca65 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 @@ -3659,3 +3659,38 @@ fn main() { "#]], ); } + +#[test] +fn rpitit_with_reference() { + check( + r#" +trait Foo { + fn foo(&self); +} + +trait Bar { + fn bar(&self) -> &impl Foo; +} + +fn baz(v: impl Bar) { + v.bar().$0 +} + "#, + expect![[r#" + me foo() (as Foo) fn(&self) + sn box Box::new(expr) + sn call function(expr) + sn const const {} + sn dbg dbg!(expr) + sn dbgr dbg!(&expr) + sn deref *expr + sn let let + sn letm let mut + sn match match expr {} + sn ref &expr + sn refm &mut expr + sn return return expr + sn unsafe unsafe {} + "#]], + ); +} 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 3c3ac9d3bbe61..3890bcad7fc61 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs @@ -95,6 +95,13 @@ pub(crate) fn goto_definition( continue; } + let parent = token.value.parent()?; + + if let Some(question_mark_conversion) = goto_question_mark_conversions(sema, &parent) { + navs.extend(def_to_nav(sema, question_mark_conversion.into())); + continue; + } + if let Some(token) = ast::String::cast(token.value.clone()) && let Some(original_token) = ast::String::cast(original_token.clone()) && let Some((analysis, fixture_analysis)) = @@ -113,8 +120,6 @@ pub(crate) fn goto_definition( }); } - let parent = token.value.parent()?; - let token_file_id = token.file_id; if let Some(token) = ast::String::cast(token.value.clone()) && let Some(x) = @@ -149,6 +154,45 @@ pub(crate) fn goto_definition( Some(RangeInfo::new(original_token.text_range(), navs)) } +/// When the `?` operator is used on `Result`, go to the `From` impl if it exists as this provides more value. +fn goto_question_mark_conversions( + sema: &Semantics<'_, RootDatabase>, + node: &SyntaxNode, +) -> Option { + let node = ast::TryExpr::cast(node.clone())?; + let try_expr_ty = sema.type_of_expr(&node.expr()?)?.adjusted(); + + let fd = FamousDefs(sema, try_expr_ty.krate(sema.db)); + let result_enum = fd.core_result_Result()?.into(); + + let (try_expr_ty_adt, try_expr_ty_args) = try_expr_ty.as_adt_with_args()?; + if try_expr_ty_adt != result_enum { + // FIXME: Support `Poll`. + return None; + } + let original_err_ty = try_expr_ty_args.get(1)?.clone()?; + + let returned_ty = sema.try_expr_returned_type(&node)?; + let (returned_adt, returned_ty_args) = returned_ty.as_adt_with_args()?; + if returned_adt != result_enum { + return None; + } + let returned_err_ty = returned_ty_args.get(1)?.clone()?; + + if returned_err_ty.could_unify_with_deeply(sema.db, &original_err_ty) { + return None; + } + + let from_trait = fd.core_convert_From()?; + let from_fn = from_trait.function(sema.db, sym::from)?; + sema.resolve_trait_impl_method( + returned_err_ty.clone(), + from_trait, + from_fn, + [returned_err_ty, original_err_ty], + ) +} + // If the token is into(), try_into(), search the definition of From, TryFrom. fn find_definition_for_known_blanket_dual_impls( sema: &Semantics<'_, RootDatabase>, @@ -4034,4 +4078,25 @@ where "#, ) } + + #[test] + fn question_mark_on_result_goes_to_conversion() { + check( + r#" +//- minicore: try, result, from + +struct Foo; +struct Bar; +impl From for Bar { + fn from(_: Foo) -> Bar { Bar } + // ^^^^ +} + +fn foo() -> Result<(), Bar> { + Err(Foo)?$0; + Ok(()) +} + "#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs index 9c7f109a626c3..9ce88484f781f 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs @@ -164,7 +164,7 @@ pub(crate) fn make_lockfile_copy( major: 1, minor: 95, patch: 0, - pre: semver::Prerelease::new("nightly").unwrap(), + pre: semver::Prerelease::new("beta").unwrap(), build: semver::BuildMetadata::EMPTY, }; 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 98d759aef2093..f5d1d009a579b 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs @@ -644,7 +644,8 @@ pub fn expr_await(expr: ast::Expr) -> ast::Expr { expr_from_text(&format!("{expr}.await")) } pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::MatchExpr { - expr_from_text(&format!("match {expr} {match_arm_list}")) + let ws = block_whitespace(&expr); + expr_from_text(&format!("match {expr}{ws}{match_arm_list}")) } pub fn expr_if( condition: ast::Expr, @@ -656,14 +657,17 @@ pub fn expr_if( Some(ast::ElseBranch::IfExpr(if_expr)) => format!("else {if_expr}"), None => String::new(), }; - expr_from_text(&format!("if {condition} {then_branch} {else_branch}")) + let ws = block_whitespace(&condition); + expr_from_text(&format!("if {condition}{ws}{then_branch} {else_branch}")) } pub fn expr_for_loop(pat: ast::Pat, expr: ast::Expr, block: ast::BlockExpr) -> ast::ForExpr { - expr_from_text(&format!("for {pat} in {expr} {block}")) + let ws = block_whitespace(&expr); + expr_from_text(&format!("for {pat} in {expr}{ws}{block}")) } pub fn expr_while_loop(condition: ast::Expr, block: ast::BlockExpr) -> ast::WhileExpr { - expr_from_text(&format!("while {condition} {block}")) + let ws = block_whitespace(&condition); + expr_from_text(&format!("while {condition}{ws}{block}")) } pub fn expr_loop(block: ast::BlockExpr) -> ast::Expr { @@ -723,6 +727,9 @@ pub fn expr_assignment(lhs: ast::Expr, rhs: ast::Expr) -> ast::BinExpr { fn expr_from_text + AstNode>(text: &str) -> E { ast_from_text(&format!("const C: () = {text};")) } +fn block_whitespace(after: &impl AstNode) -> &'static str { + if after.syntax().text().contains_char('\n') { "\n" } else { " " } +} pub fn expr_let(pattern: ast::Pat, expr: ast::Expr) -> ast::LetExpr { ast_from_text(&format!("const _: () = while let {pattern} = {expr} {{}};")) } @@ -880,8 +887,9 @@ pub fn ref_pat(pat: ast::Pat) -> ast::RefPat { pub fn match_arm(pat: ast::Pat, guard: Option, expr: ast::Expr) -> ast::MatchArm { let comma_str = if expr.is_block_like() { "" } else { "," }; + let ws = guard.as_ref().filter(|_| expr.is_block_like()).map_or(" ", block_whitespace); return match guard { - Some(guard) => from_text(&format!("{pat} {guard} => {expr}{comma_str}")), + Some(guard) => from_text(&format!("{pat} {guard} =>{ws}{expr}{comma_str}")), None => from_text(&format!("{pat} => {expr}{comma_str}")), }; @@ -890,19 +898,6 @@ pub fn match_arm(pat: ast::Pat, guard: Option, expr: ast::Expr) } } -pub fn match_arm_with_guard( - pats: impl IntoIterator, - guard: ast::Expr, - expr: ast::Expr, -) -> ast::MatchArm { - let pats_str = pats.into_iter().join(" | "); - return from_text(&format!("{pats_str} if {guard} => {expr}")); - - fn from_text(text: &str) -> ast::MatchArm { - ast_from_text(&format!("fn f() {{ match () {{{text}}} }}")) - } -} - pub fn match_guard(condition: ast::Expr) -> ast::MatchGuard { return from_text(&format!("if {condition}")); diff --git a/src/tools/rust-analyzer/editors/code/package-lock.json b/src/tools/rust-analyzer/editors/code/package-lock.json index d3fbea2336b0b..be0bdc8d55bbf 100644 --- a/src/tools/rust-analyzer/editors/code/package-lock.json +++ b/src/tools/rust-analyzer/editors/code/package-lock.json @@ -749,9 +749,9 @@ } }, "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -810,9 +810,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -934,29 +934,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1851,9 +1828,9 @@ } }, "node_modules/@vscode/vsce/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -3463,9 +3440,9 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -3907,17 +3884,40 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/glob/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4895,13 +4895,13 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -6840,9 +6840,9 @@ } }, "node_modules/vscode-languageclient/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" diff --git a/src/tools/rust-analyzer/lib/lsp-server/src/req_queue.rs b/src/tools/rust-analyzer/lib/lsp-server/src/req_queue.rs index c216864bee8c2..84748bbca8d77 100644 --- a/src/tools/rust-analyzer/lib/lsp-server/src/req_queue.rs +++ b/src/tools/rust-analyzer/lib/lsp-server/src/req_queue.rs @@ -18,6 +18,12 @@ impl Default for ReqQueue { } } +impl ReqQueue { + pub fn has_pending(&self) -> bool { + self.incoming.has_pending() || self.outgoing.has_pending() + } +} + #[derive(Debug)] pub struct Incoming { pending: HashMap, @@ -51,6 +57,10 @@ impl Incoming { pub fn is_completed(&self, id: &RequestId) -> bool { !self.pending.contains_key(id) } + + pub fn has_pending(&self) -> bool { + !self.pending.is_empty() + } } impl Outgoing { @@ -64,4 +74,8 @@ impl Outgoing { pub fn complete(&mut self, id: RequestId) -> Option { self.pending.remove(&id) } + + pub fn has_pending(&self) -> bool { + !self.pending.is_empty() + } } diff --git a/src/tools/rust-analyzer/lib/smol_str/CHANGELOG.md b/src/tools/rust-analyzer/lib/smol_str/CHANGELOG.md index 4aa25fa13446f..6327275d07dcb 100644 --- a/src/tools/rust-analyzer/lib/smol_str/CHANGELOG.md +++ b/src/tools/rust-analyzer/lib/smol_str/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.3.6 - 2026-03-04 +- Fix the `borsh` feature. + ## 0.3.5 - 2026-01-08 - Optimise `SmolStr::clone` 4-5x speedup inline, 0.5x heap (slow down). diff --git a/src/tools/rust-analyzer/lib/smol_str/Cargo.toml b/src/tools/rust-analyzer/lib/smol_str/Cargo.toml index 4e7844b49e195..22068fe8418b4 100644 --- a/src/tools/rust-analyzer/lib/smol_str/Cargo.toml +++ b/src/tools/rust-analyzer/lib/smol_str/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "smol_str" -version = "0.3.5" +version = "0.3.6" description = "small-string optimized string type with O(1) clone" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/rust-analyzer/tree/master/lib/smol_str" diff --git a/src/tools/rust-analyzer/rust-version b/src/tools/rust-analyzer/rust-version index b6e1b2bc55df4..7c89bcb9ab045 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -c78a29473a68f07012904af11c92ecffa68fcc75 +f8704be04fe1150527fc2cf21dd44327f0fe87fb diff --git a/src/tools/rust-analyzer/triagebot.toml b/src/tools/rust-analyzer/triagebot.toml index ac4efd0a24bcb..5fd97f52d8c2c 100644 --- a/src/tools/rust-analyzer/triagebot.toml +++ b/src/tools/rust-analyzer/triagebot.toml @@ -24,4 +24,5 @@ labels = ["has-merge-commits", "S-waiting-on-author"] [transfer] # Canonicalize issue numbers to avoid closing the wrong issue when upstreaming this subtree -[canonicalize-issue-links] +[issue-links] +check-commits = "uncanonicalized" diff --git a/src/tools/rust-analyzer/xtask/src/codegen/grammar/ast_src.rs b/src/tools/rust-analyzer/xtask/src/codegen/grammar/ast_src.rs index 205072a6ce0cf..564d9cc24efe8 100644 --- a/src/tools/rust-analyzer/xtask/src/codegen/grammar/ast_src.rs +++ b/src/tools/rust-analyzer/xtask/src/codegen/grammar/ast_src.rs @@ -189,9 +189,9 @@ pub(crate) fn generate_kind_src( } } }); - PUNCT.iter().zip(used_puncts).filter(|(_, used)| !used).for_each(|((punct, _), _)| { + if let Some(punct) = PUNCT.iter().zip(used_puncts).find(|(_, used)| !used) { panic!("Punctuation {punct:?} is not used in grammar"); - }); + } keywords.extend(RESERVED.iter().copied()); keywords.sort(); keywords.dedup();