Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #153507 - JonathanBrouwer:rollup-ki59UTE, r=JonathanBrouwer
Rollup of 14 pull requests Successful merges: - rust-lang/rust#153466 (`rust-analyzer` subtree update) - rust-lang/rust#151280 (Fix incorrect trailing comma suggested in no_accessible_fields) - rust-lang/rust#152593 (Box in `ValTreeKind::Branch(Box<[I::Const]>)` changed to `List`) - rust-lang/rust#153174 (std: add wasm64 to sync::Once and thread_parking atomics cfg guards) - rust-lang/rust#153485 (libcore float tests: replace macro shadowing by const-compatible macro) - rust-lang/rust#153495 (Fix ICE in `offset_of!` error recovery) - rust-lang/rust#152040 (Do not emit ConstEvaluatable goals if type-const) - rust-lang/rust#152741 (Suppress invalid suggestions in destructuring assignment) - rust-lang/rust#153189 (refactor: move `check_align` to `parse_alignment`) - rust-lang/rust#153230 (Roll rustfmt reviewers for in-tree rustfmt) - rust-lang/rust#153445 (Consider try blocks as block-like for overflowed expr) - rust-lang/rust#153452 (Cleanup unused diagnostic emission methods) - rust-lang/rust#153476 (bootstrap.py: fix typo "parallle") - rust-lang/rust#153483 (Preserve parentheses around `Fn` trait bounds in pretty printer)
bors 7 weeks ago
parent b477a05 · parent a6fc78b · commit 112f2f5
-rw-r--r--.codecov.yml10
-rw-r--r--.github/workflows/coverage.yaml44
l---------AGENTS.md1
-rw-r--r--CLAUDE.md40
-rw-r--r--Cargo.lock2
-rw-r--r--crates/hir-def/src/visibility.rs2
-rw-r--r--crates/hir-expand/src/builtin/fn_macro.rs5
-rw-r--r--crates/hir-ty/src/infer/expr.rs2
-rw-r--r--crates/hir-ty/src/infer/op.rs12
-rw-r--r--crates/hir-ty/src/lower.rs112
-rw-r--r--crates/hir-ty/src/lower/path.rs39
-rw-r--r--crates/hir-ty/src/next_solver/abi.rs3
-rw-r--r--crates/hir-ty/src/tests/never_type.rs46
-rw-r--r--crates/hir-ty/src/tests/regression.rs20
-rw-r--r--crates/hir-ty/src/tests/traits.rs34
-rw-r--r--crates/hir/src/lib.rs18
-rw-r--r--crates/hir/src/semantics.rs22
-rw-r--r--crates/ide-assists/src/handlers/add_missing_match_arms.rs39
-rw-r--r--crates/ide-assists/src/handlers/destructure_struct_binding.rs231
-rw-r--r--crates/ide-assists/src/handlers/destructure_tuple_binding.rs1
-rw-r--r--crates/ide-assists/src/handlers/move_guard.rs3
-rw-r--r--crates/ide-assists/src/handlers/replace_if_let_with_match.rs15
-rw-r--r--crates/ide-completion/src/completions/postfix.rs64
-rw-r--r--crates/ide-completion/src/tests/expression.rs35
-rw-r--r--crates/ide/src/goto_definition.rs69
-rw-r--r--crates/project-model/src/cargo_config_file.rs2
-rw-r--r--crates/syntax/src/ast/make.rs31
-rw-r--r--editors/code/package-lock.json94
-rw-r--r--lib/lsp-server/src/req_queue.rs14
-rw-r--r--lib/smol_str/CHANGELOG.md3
-rw-r--r--lib/smol_str/Cargo.toml2
-rw-r--r--rust-version2
-rw-r--r--triagebot.toml3
-rw-r--r--xtask/src/codegen/grammar/ast_src.rs4
34 files changed, 817 insertions, 207 deletions
diff --git a/.codecov.yml b/.codecov.yml
new file mode 100644
index 0000000000..68eacb7d08
--- /dev/null
+++ b/.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/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml
new file mode 100644
index 0000000000..bd4edba747
--- /dev/null
+++ b/.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/AGENTS.md b/AGENTS.md
new file mode 120000
index 0000000000..681311eb9c
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1 @@
+CLAUDE.md \ No newline at end of file
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000000..e8f699d928
--- /dev/null
+++ b/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 <crate> # 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/Cargo.lock b/Cargo.lock
index 9db4dd7cb1..f31dda4a10 100644
--- a/Cargo.lock
+++ b/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/crates/hir-def/src/visibility.rs b/crates/hir-def/src/visibility.rs
index ccd001df69..cb5eed1b8b 100644
--- a/crates/hir-def/src/visibility.rs
+++ b/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/crates/hir-expand/src/builtin/fn_macro.rs b/crates/hir-expand/src/builtin/fn_macro.rs
index ae8eef2bbb..949b22b0ad 100644
--- a/crates/hir-expand/src/builtin/fn_macro.rs
+++ b/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/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index 16e7d51e87..45b181eff8 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/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/crates/hir-ty/src/infer/op.rs b/crates/hir-ty/src/infer/op.rs
index c79c828cd4..95d63ffb50 100644
--- a/crates/hir-ty/src/infer/op.rs
+++ b/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<input_tys>`
diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs
index a0945c3bdc..c49e943437 100644
--- a/crates/hir-ty/src/lower.rs
+++ b/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<StoredEarlyBinder<(TypeAliasId, StoredGenericArgs)>>,
+ },
+ NotFound,
Cycle,
}
@@ -1708,7 +1713,7 @@ fn resolve_type_param_assoc_type_shorthand(
def: GenericDefId,
param: TypeParamId,
assoc_name: Name,
-) -> Result<StoredEarlyBinder<(TypeAliasId, StoredGenericArgs)>, 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<StoredEarlyBinder<(TypeAliasId, StoredGenericArgs)>, 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/crates/hir-ty/src/lower/path.rs b/crates/hir-ty/src/lower/path.rs
index d47d696259..79f29d370f 100644
--- a/crates/hir-ty/src/lower/path.rs
+++ b/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/crates/hir-ty/src/next_solver/abi.rs b/crates/hir-ty/src/next_solver/abi.rs
index 80d1ea4aa4..1813abab86 100644
--- a/crates/hir-ty/src/next_solver/abi.rs
+++ b/crates/hir-ty/src/next_solver/abi.rs
@@ -62,7 +62,6 @@ impl<'db> rustc_type_ir::inherent::Abi<DbInterner<'db>> 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/crates/hir-ty/src/tests/never_type.rs b/crates/hir-ty/src/tests/never_type.rs
index e4e7b4cc38..993293bb56 100644
--- a/crates/hir-ty/src/tests/never_type.rs
+++ b/crates/hir-ty/src/tests/never_type.rs
@@ -803,6 +803,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(
r#"
@@ -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/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs
index 658c304aac..1939db0ef5 100644
--- a/crates/hir-ty/src/tests/regression.rs
+++ b/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 = <Self as Collection>::Item> {
+ type Item;
+ fn contains(&self, item: &<Self as Collection>::Item);
+}
+
+fn contains_0<S: Collection<Item = i32>>(points: &S) {
+ points.contains(&0)
+}
+ "#,
+ );
+}
diff --git a/crates/hir-ty/src/tests/traits.rs b/crates/hir-ty/src/tests/traits.rs
index cdf7b40003..74e9a8dac0 100644
--- a/crates/hir-ty/src/tests/traits.rs
+++ b/crates/hir-ty/src/tests/traits.rs
@@ -2218,6 +2218,40 @@ fn test() {
}
#[test]
+fn tuple_struct_constructor_as_fn_trait() {
+ check_types(
+ r#"
+//- minicore: fn
+struct S(u32, u64);
+
+fn takes_fn<F: Fn(u32, u64) -> 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<F: Fn(u32) -> E>(f: F) -> E { f(1) }
+
+fn test() {
+ takes_fn(E::A);
+ //^^^^^^^^^^^^^^ E
+}
+"#,
+ );
+}
+
+#[test]
fn fn_item_fn_trait() {
check_types(
r#"
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 9dbee16dae..11d79e2d7b 100644
--- a/crates/hir/src/lib.rs
+++ b/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<Option<Type<'db>>>)> {
+ 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<BuiltinType> {
self.ty.as_builtin().map(|inner| BuiltinType { inner })
}
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 4bc757da44..1cf3b98160 100644
--- a/crates/hir/src/semantics.rs
+++ b/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<Type<'db>> {
+ 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/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/crates/ide-assists/src/handlers/add_missing_match_arms.rs
index 10c3ff0e4d..c0ce057d77 100644
--- a/crates/ide-assists/src/handlers/add_missing_match_arms.rs
+++ b/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(());
@@ -1614,6 +1613,38 @@ fn foo(t: Test) {
});
}"#,
);
+
+ 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
+ });
+}"#,
+ );
}
#[test]
diff --git a/crates/ide-assists/src/handlers/destructure_struct_binding.rs b/crates/ide-assists/src/handlers/destructure_struct_binding.rs
index bb5d112210..4c4cee1d78 100644
--- a/crates/ide-assists/src/handlers/destructure_struct_binding.rs
+++ b/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::<ast::IdentPat>()?;
- let data = collect_data(ident_pat, ctx)?;
+ let target = ctx.find_node_at_offset::<Target>()?;
+ 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<hir::Type<'db>> {
+ 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<Self> {
+ 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<StructEditData> {
- 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<StructEditData> {
+ 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<Str
}
let is_ref = ty.is_reference();
- let need_record_field_name = ident_pat
+ let need_record_field_name = target
.syntax()
.parent()
.and_then(ast::RecordPatField::cast)
.is_some_and(|field| field.colon_token().is_none());
- let usages = ctx
- .sema
- .to_def(&ident_pat)
+ let def = match &target {
+ Target::IdentPat(pat) => 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<Str
})
.unwrap_or_default();
- let names_in_scope = get_names_in_scope(ctx, &ident_pat, &usages).unwrap_or_default();
+ let names_in_scope = get_names_in_scope(ctx, &target, &usages).unwrap_or_default();
Some(StructEditData {
- name: ident_pat.name()?,
- ident_pat,
+ name: target.name()?,
+ target,
kind,
struct_def_path,
usages,
@@ -155,7 +251,7 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option<Str
fn get_names_in_scope(
ctx: &AssistContext<'_>,
- ident_pat: &ast::IdentPat,
+ target: &Target,
usages: &[FileReference],
) -> Option<FxHashSet<SmolStr>> {
fn last_usage(usages: &[FileReference]) -> Option<SyntaxNode> {
@@ -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)> {
@@ -699,6 +784,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(
destructure_struct_binding,
diff --git a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
index b8dc59f87d..d51a3a26a3 100644
--- a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
+++ b/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<RefType>,
field_names: Vec<String>,
diff --git a/crates/ide-assists/src/handlers/move_guard.rs b/crates/ide-assists/src/handlers/move_guard.rs
index fdc5a0fbda..b4c347ff36 100644
--- a/crates/ide-assists/src/handlers/move_guard.rs
+++ b/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/crates/ide-assists/src/handlers/replace_if_let_with_match.rs b/crates/ide-assists/src/handlers/replace_if_let_with_match.rs
index dcadb5368d..8ff30fce5b 100644
--- a/crates/ide-assists/src/handlers/replace_if_let_with_match.rs
+++ b/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/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index a58592b136..ea53aef40c 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/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;
@@ -915,6 +919,30 @@ fn main() {
sn unsafe unsafe {}
"#]],
);
+ check(
+ r#"
+fn main() {
+ match 2 {
+ bar => &bar.l$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_edit(
"let",
r#"
diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs
index 5fef8c44de..8e50ef10ec 100644
--- a/crates/ide-completion/src/tests/expression.rs
+++ b/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/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 3c3ac9d3bb..3890bcad7f 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/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<hir::Function> {
+ 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<Result>`.
+ 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<Foo> for Bar {
+ fn from(_: Foo) -> Bar { Bar }
+ // ^^^^
+}
+
+fn foo() -> Result<(), Bar> {
+ Err(Foo)?$0;
+ Ok(())
+}
+ "#,
+ );
+ }
}
diff --git a/crates/project-model/src/cargo_config_file.rs b/crates/project-model/src/cargo_config_file.rs
index 9c7f109a62..9ce88484f7 100644
--- a/crates/project-model/src/cargo_config_file.rs
+++ b/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/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 98d759aef2..f5d1d009a5 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/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<E: Into<ast::Expr> + 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<ast::MatchGuard>, 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<ast::MatchGuard>, expr: ast::Expr)
}
}
-pub fn match_arm_with_guard(
- pats: impl IntoIterator<Item = ast::Pat>,
- 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/editors/code/package-lock.json b/editors/code/package-lock.json
index d3fbea2336..be0bdc8d55 100644
--- a/editors/code/package-lock.json
+++ b/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/lib/lsp-server/src/req_queue.rs b/lib/lsp-server/src/req_queue.rs
index c216864bee..84748bbca8 100644
--- a/lib/lsp-server/src/req_queue.rs
+++ b/lib/lsp-server/src/req_queue.rs
@@ -18,6 +18,12 @@ impl<I, O> Default for ReqQueue<I, O> {
}
}
+impl<I, O> ReqQueue<I, O> {
+ pub fn has_pending(&self) -> bool {
+ self.incoming.has_pending() || self.outgoing.has_pending()
+ }
+}
+
#[derive(Debug)]
pub struct Incoming<I> {
pending: HashMap<RequestId, I>,
@@ -51,6 +57,10 @@ impl<I> Incoming<I> {
pub fn is_completed(&self, id: &RequestId) -> bool {
!self.pending.contains_key(id)
}
+
+ pub fn has_pending(&self) -> bool {
+ !self.pending.is_empty()
+ }
}
impl<O> Outgoing<O> {
@@ -64,4 +74,8 @@ impl<O> Outgoing<O> {
pub fn complete(&mut self, id: RequestId) -> Option<O> {
self.pending.remove(&id)
}
+
+ pub fn has_pending(&self) -> bool {
+ !self.pending.is_empty()
+ }
}
diff --git a/lib/smol_str/CHANGELOG.md b/lib/smol_str/CHANGELOG.md
index 4aa25fa134..6327275d07 100644
--- a/lib/smol_str/CHANGELOG.md
+++ b/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/lib/smol_str/Cargo.toml b/lib/smol_str/Cargo.toml
index 4e7844b49e..22068fe841 100644
--- a/lib/smol_str/Cargo.toml
+++ b/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/rust-version b/rust-version
index b6e1b2bc55..7c89bcb9ab 100644
--- a/rust-version
+++ b/rust-version
@@ -1 +1 @@
-c78a29473a68f07012904af11c92ecffa68fcc75
+f8704be04fe1150527fc2cf21dd44327f0fe87fb
diff --git a/triagebot.toml b/triagebot.toml
index ac4efd0a24..5fd97f52d8 100644
--- a/triagebot.toml
+++ b/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/xtask/src/codegen/grammar/ast_src.rs b/xtask/src/codegen/grammar/ast_src.rs
index 205072a6ce..564d9cc24e 100644
--- a/xtask/src/codegen/grammar/ast_src.rs
+++ b/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();