Auto merge of #144603 - lnicola:sync-from-ra, r=lnicola

`rust-analyzer` subtree update

Subtree update of `rust-analyzer` to 511c999bea.

Created using https://github.com/rust-lang/josh-sync.

r? `@ghost`
This commit is contained in:
bors 2025-07-28 19:39:43 +00:00
commit 498ae9fed2
97 changed files with 2183 additions and 1425 deletions

View file

@ -0,0 +1,20 @@
name: rustc-pull
on:
workflow_dispatch:
schedule:
# Run at 04:00 UTC every Monday and Thursday
- cron: '0 4 * * 1,4'
jobs:
pull:
if: github.repository == 'rust-lang/rust-analyzer'
uses: rust-lang/josh-sync/.github/workflows/rustc-pull.yml@main
with:
zulip-stream-id: 185405
zulip-bot-email: "rust-analyzer-ci-bot@rust-lang.zulipchat.com"
pr-base-branch: master
branch-name: rustc-pull
secrets:
zulip-api-token: ${{ secrets.ZULIP_API_TOKEN }}
token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -395,15 +395,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "directories"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
dependencies = [
"dirs-sys",
]
[[package]] [[package]]
name = "dirs" name = "dirs"
version = "6.0.0" version = "6.0.0"
@ -1268,7 +1259,7 @@ dependencies = [
"expect-test", "expect-test",
"intern", "intern",
"parser", "parser",
"ra-ap-rustc_lexer 0.122.0", "ra-ap-rustc_lexer 0.123.0",
"rustc-hash 2.1.1", "rustc-hash 2.1.1",
"smallvec", "smallvec",
"span", "span",
@ -1504,7 +1495,7 @@ dependencies = [
"drop_bomb", "drop_bomb",
"edition", "edition",
"expect-test", "expect-test",
"ra-ap-rustc_lexer 0.122.0", "ra-ap-rustc_lexer 0.123.0",
"rustc-literal-escaper", "rustc-literal-escaper",
"stdx", "stdx",
"tracing", "tracing",
@ -1614,7 +1605,7 @@ dependencies = [
"object", "object",
"paths", "paths",
"proc-macro-test", "proc-macro-test",
"ra-ap-rustc_lexer 0.122.0", "ra-ap-rustc_lexer 0.123.0",
"span", "span",
"syntax-bridge", "syntax-bridge",
"tt", "tt",
@ -1688,6 +1679,7 @@ dependencies = [
"serde_json", "serde_json",
"span", "span",
"stdx", "stdx",
"temp-dir",
"toolchain", "toolchain",
"tracing", "tracing",
"triomphe", "triomphe",
@ -1756,9 +1748,9 @@ dependencies = [
[[package]] [[package]]
name = "ra-ap-rustc_abi" name = "ra-ap-rustc_abi"
version = "0.122.0" version = "0.123.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb01e1fec578003c85481c1cad4ff8cd8195b07c2dc85ae3f716108507ae15d5" checksum = "f18c877575c259d127072e9bfc41d985202262fb4d6bfdae3d1252147c2562c2"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"ra-ap-rustc_hashes", "ra-ap-rustc_hashes",
@ -1768,18 +1760,18 @@ dependencies = [
[[package]] [[package]]
name = "ra-ap-rustc_hashes" name = "ra-ap-rustc_hashes"
version = "0.122.0" version = "0.123.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0ec056e72a472ffef8761ce96ece6c626eb07368c09d0105b6df30d27d07673" checksum = "2439ed1df3472443133b66949f81080dff88089b42f825761455463709ee1cad"
dependencies = [ dependencies = [
"rustc-stable-hash", "rustc-stable-hash",
] ]
[[package]] [[package]]
name = "ra-ap-rustc_index" name = "ra-ap-rustc_index"
version = "0.122.0" version = "0.123.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fcdd1001db0295e59052e9f53aeda588bbe81e362534f4687d41bd44777b5a7" checksum = "57a24fe0be21be1f8ebc21dcb40129214fb4cefb0f2753f3d46b6dbe656a1a45"
dependencies = [ dependencies = [
"ra-ap-rustc_index_macros", "ra-ap-rustc_index_macros",
"smallvec", "smallvec",
@ -1787,9 +1779,9 @@ dependencies = [
[[package]] [[package]]
name = "ra-ap-rustc_index_macros" name = "ra-ap-rustc_index_macros"
version = "0.122.0" version = "0.123.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728d64dd98e25530b32e3f7c7c1e844e52722b269360daa1cdeba9dff9727a26" checksum = "844a27ddcad0116facae2df8e741fd788662cf93dc13029cd864f2b8013b81f9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1809,9 +1801,9 @@ dependencies = [
[[package]] [[package]]
name = "ra-ap-rustc_lexer" name = "ra-ap-rustc_lexer"
version = "0.122.0" version = "0.123.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "415f0821f512608d825b3215489a6a6a2c18ed9f0045953d514e7ec23d4b90ab" checksum = "2b734cfcb577d09877799a22742f1bd398be6c00bc428d9de56d48d11ece5771"
dependencies = [ dependencies = [
"memchr", "memchr",
"unicode-properties", "unicode-properties",
@ -1830,9 +1822,9 @@ dependencies = [
[[package]] [[package]]
name = "ra-ap-rustc_pattern_analysis" name = "ra-ap-rustc_pattern_analysis"
version = "0.122.0" version = "0.123.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4657fcfdfe06e2a02ec8180d4e7c95aecf4811ba50367e363d1a2300b7623284" checksum = "75b0ee1f059b9dea0818c6c7267478926eee95ba4c7dcf89c8db32fa165d3904"
dependencies = [ dependencies = [
"ra-ap-rustc_index", "ra-ap-rustc_index",
"rustc-hash 2.1.1", "rustc-hash 2.1.1",
@ -2293,6 +2285,12 @@ dependencies = [
"tt", "tt",
] ]
[[package]]
name = "temp-dir"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83176759e9416cf81ee66cb6508dbfe9c96f20b8b56265a39917551c23c70964"
[[package]] [[package]]
name = "tenthash" name = "tenthash"
version = "1.1.0" version = "1.1.0"
@ -2592,7 +2590,7 @@ version = "0.0.0"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"intern", "intern",
"ra-ap-rustc_lexer 0.122.0", "ra-ap-rustc_lexer 0.123.0",
"stdx", "stdx",
"text-size", "text-size",
] ]
@ -3105,7 +3103,6 @@ name = "xtask"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"directories",
"edition", "edition",
"either", "either",
"flate2", "flate2",

View file

@ -89,11 +89,11 @@ vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" }
vfs = { path = "./crates/vfs", version = "0.0.0" } vfs = { path = "./crates/vfs", version = "0.0.0" }
edition = { path = "./crates/edition", version = "0.0.0" } edition = { path = "./crates/edition", version = "0.0.0" }
ra-ap-rustc_lexer = { version = "0.122", default-features = false } ra-ap-rustc_lexer = { version = "0.123", default-features = false }
ra-ap-rustc_parse_format = { version = "0.121", default-features = false } ra-ap-rustc_parse_format = { version = "0.121", default-features = false }
ra-ap-rustc_index = { version = "0.122", default-features = false } ra-ap-rustc_index = { version = "0.123", default-features = false }
ra-ap-rustc_abi = { version = "0.122", default-features = false } ra-ap-rustc_abi = { version = "0.123", default-features = false }
ra-ap-rustc_pattern_analysis = { version = "0.122", default-features = false } ra-ap-rustc_pattern_analysis = { version = "0.123", default-features = false }
# local crates that aren't published to crates.io. These should not have versions. # local crates that aren't published to crates.io. These should not have versions.
@ -156,6 +156,7 @@ smallvec = { version = "1.15.1", features = [
"const_generics", "const_generics",
] } ] }
smol_str = "0.3.2" smol_str = "0.3.2"
temp-dir = "0.1.16"
text-size = "1.1.1" text-size = "1.1.1"
tracing = "0.1.41" tracing = "0.1.41"
tracing-tree = "0.4.0" tracing-tree = "0.4.0"

View file

@ -30,6 +30,7 @@ pub type ProcMacroPaths =
pub enum ProcMacroLoadingError { pub enum ProcMacroLoadingError {
Disabled, Disabled,
FailedToBuild, FailedToBuild,
ExpectedProcMacroArtifact,
MissingDylibPath, MissingDylibPath,
NotYetBuilt, NotYetBuilt,
NoProcMacros, NoProcMacros,
@ -39,7 +40,8 @@ impl ProcMacroLoadingError {
pub fn is_hard_error(&self) -> bool { pub fn is_hard_error(&self) -> bool {
match self { match self {
ProcMacroLoadingError::Disabled | ProcMacroLoadingError::NotYetBuilt => false, ProcMacroLoadingError::Disabled | ProcMacroLoadingError::NotYetBuilt => false,
ProcMacroLoadingError::FailedToBuild ProcMacroLoadingError::ExpectedProcMacroArtifact
| ProcMacroLoadingError::FailedToBuild
| ProcMacroLoadingError::MissingDylibPath | ProcMacroLoadingError::MissingDylibPath
| ProcMacroLoadingError::NoProcMacros | ProcMacroLoadingError::NoProcMacros
| ProcMacroLoadingError::ProcMacroSrvError(_) => true, | ProcMacroLoadingError::ProcMacroSrvError(_) => true,
@ -51,10 +53,16 @@ impl Error for ProcMacroLoadingError {}
impl fmt::Display for ProcMacroLoadingError { impl fmt::Display for ProcMacroLoadingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
ProcMacroLoadingError::ExpectedProcMacroArtifact => {
write!(f, "proc-macro crate did not build proc-macro artifact")
}
ProcMacroLoadingError::Disabled => write!(f, "proc-macro expansion is disabled"), ProcMacroLoadingError::Disabled => write!(f, "proc-macro expansion is disabled"),
ProcMacroLoadingError::FailedToBuild => write!(f, "proc-macro failed to build"), ProcMacroLoadingError::FailedToBuild => write!(f, "proc-macro failed to build"),
ProcMacroLoadingError::MissingDylibPath => { ProcMacroLoadingError::MissingDylibPath => {
write!(f, "proc-macro crate build data is missing a dylib path") write!(
f,
"proc-macro crate built but the dylib path is missing, this indicates a problem with your build system."
)
} }
ProcMacroLoadingError::NotYetBuilt => write!(f, "proc-macro not yet built"), ProcMacroLoadingError::NotYetBuilt => write!(f, "proc-macro not yet built"),
ProcMacroLoadingError::NoProcMacros => { ProcMacroLoadingError::NoProcMacros => {

View file

@ -16,7 +16,7 @@ use std::{
use cfg::{CfgExpr, CfgOptions}; use cfg::{CfgExpr, CfgOptions};
use either::Either; use either::Either;
use hir_expand::{ExpandError, InFile, MacroCallId, mod_path::ModPath, name::Name}; use hir_expand::{InFile, MacroCallId, mod_path::ModPath, name::Name};
use la_arena::{Arena, ArenaMap}; use la_arena::{Arena, ArenaMap};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use smallvec::SmallVec; use smallvec::SmallVec;
@ -281,7 +281,6 @@ struct FormatTemplate {
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub enum ExpressionStoreDiagnostics { pub enum ExpressionStoreDiagnostics {
InactiveCode { node: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions }, InactiveCode { node: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions },
MacroError { node: InFile<MacroCallPtr>, err: ExpandError },
UnresolvedMacroCall { node: InFile<MacroCallPtr>, path: ModPath }, UnresolvedMacroCall { node: InFile<MacroCallPtr>, path: ModPath },
UnreachableLabel { node: InFile<AstPtr<ast::Lifetime>>, name: Name }, UnreachableLabel { node: InFile<AstPtr<ast::Lifetime>>, name: Name },
AwaitOutsideOfAsync { node: InFile<AstPtr<ast::AwaitExpr>>, location: String }, AwaitOutsideOfAsync { node: InFile<AstPtr<ast::AwaitExpr>>, location: String },

View file

@ -960,37 +960,28 @@ impl ExprCollector<'_> {
impl_trait_lower_fn: ImplTraitLowerFn<'_>, impl_trait_lower_fn: ImplTraitLowerFn<'_>,
) -> TypeBound { ) -> TypeBound {
match node.kind() { match node.kind() {
ast::TypeBoundKind::PathType(path_type) => { ast::TypeBoundKind::PathType(binder, path_type) => {
let m = match node.question_mark_token() { let binder = match binder.and_then(|it| it.generic_param_list()) {
Some(_) => TraitBoundModifier::Maybe,
None => TraitBoundModifier::None,
};
self.lower_path_type(&path_type, impl_trait_lower_fn)
.map(|p| {
TypeBound::Path(self.alloc_path(p, AstPtr::new(&path_type).upcast()), m)
})
.unwrap_or(TypeBound::Error)
}
ast::TypeBoundKind::ForType(for_type) => {
let lt_refs = match for_type.generic_param_list() {
Some(gpl) => gpl Some(gpl) => gpl
.lifetime_params() .lifetime_params()
.flat_map(|lp| lp.lifetime().map(|lt| Name::new_lifetime(&lt.text()))) .flat_map(|lp| lp.lifetime().map(|lt| Name::new_lifetime(&lt.text())))
.collect(), .collect(),
None => ThinVec::default(), None => ThinVec::default(),
}; };
let path = for_type.ty().and_then(|ty| match &ty { let m = match node.question_mark_token() {
ast::Type::PathType(path_type) => { Some(_) => TraitBoundModifier::Maybe,
self.lower_path_type(path_type, impl_trait_lower_fn).map(|p| (p, ty)) None => TraitBoundModifier::None,
} };
_ => None, self.lower_path_type(&path_type, impl_trait_lower_fn)
}); .map(|p| {
match path { let path = self.alloc_path(p, AstPtr::new(&path_type).upcast());
Some((p, ty)) => { if binder.is_empty() {
TypeBound::ForLifetime(lt_refs, self.alloc_path(p, AstPtr::new(&ty))) TypeBound::Path(path, m)
} } else {
None => TypeBound::Error, TypeBound::ForLifetime(binder, path)
} }
})
.unwrap_or(TypeBound::Error)
} }
ast::TypeBoundKind::Use(gal) => TypeBound::Use( ast::TypeBoundKind::Use(gal) => TypeBound::Use(
gal.use_bound_generic_args() gal.use_bound_generic_args()
@ -1981,13 +1972,7 @@ impl ExprCollector<'_> {
return collector(self, None); return collector(self, None);
} }
}; };
if record_diagnostics { // No need to push macro and parsing errors as they'll be recreated from `macro_calls()`.
if let Some(err) = res.err {
self.store
.diagnostics
.push(ExpressionStoreDiagnostics::MacroError { node: macro_call_ptr, err });
}
}
match res.value { match res.value {
Some((mark, expansion)) => { Some((mark, expansion)) => {
@ -1997,10 +1982,6 @@ impl ExprCollector<'_> {
self.store.expansions.insert(macro_call_ptr, macro_file); self.store.expansions.insert(macro_call_ptr, macro_file);
} }
if record_diagnostics {
// FIXME: Report parse errors here
}
let id = collector(self, expansion.map(|it| it.tree())); let id = collector(self, expansion.map(|it| it.tree()));
self.expander.exit(mark); self.expander.exit(mark);
id id

View file

@ -180,17 +180,18 @@ impl GenericParamsCollector {
continue; continue;
}; };
let lifetimes: Option<Box<_>> = pred.generic_param_list().map(|param_list| { let lifetimes: Option<Box<_>> =
// Higher-Ranked Trait Bounds pred.for_binder().and_then(|it| it.generic_param_list()).map(|param_list| {
param_list // Higher-Ranked Trait Bounds
.lifetime_params() param_list
.map(|lifetime_param| { .lifetime_params()
lifetime_param .map(|lifetime_param| {
.lifetime() lifetime_param
.map_or_else(Name::missing, |lt| Name::new_lifetime(&lt.text())) .lifetime()
}) .map_or_else(Name::missing, |lt| Name::new_lifetime(&lt.text()))
.collect() })
}); .collect()
});
for bound in pred.type_bound_list().iter().flat_map(|l| l.bounds()) { for bound in pred.type_bound_list().iter().flat_map(|l| l.bounds()) {
self.lower_type_bound_as_predicate(ec, bound, lifetimes.as_deref(), target); self.lower_type_bound_as_predicate(ec, bound, lifetimes.as_deref(), target);
} }

View file

@ -27,7 +27,7 @@ pub enum Path {
} }
// This type is being used a lot, make sure it doesn't grow unintentionally. // This type is being used a lot, make sure it doesn't grow unintentionally.
#[cfg(target_arch = "x86_64")] #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
const _: () = { const _: () = {
assert!(size_of::<Path>() == 24); assert!(size_of::<Path>() == 24);
assert!(size_of::<Option<Path>>() == 24); assert!(size_of::<Option<Path>>() == 24);

View file

@ -148,7 +148,7 @@ pub enum TypeRef {
Error, Error,
} }
#[cfg(target_arch = "x86_64")] #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
const _: () = assert!(size_of::<TypeRef>() == 24); const _: () = assert!(size_of::<TypeRef>() == 24);
pub type TypeRefId = Idx<TypeRef>; pub type TypeRefId = Idx<TypeRef>;

View file

@ -175,8 +175,9 @@ impl ExprValidator {
}); });
} }
let receiver_ty = self.infer[*receiver].clone(); if let Some(receiver_ty) = self.infer.type_of_expr_with_adjust(*receiver) {
checker.prev_receiver_ty = Some(receiver_ty); checker.prev_receiver_ty = Some(receiver_ty.clone());
}
} }
} }
@ -187,7 +188,9 @@ impl ExprValidator {
arms: &[MatchArm], arms: &[MatchArm],
db: &dyn HirDatabase, db: &dyn HirDatabase,
) { ) {
let scrut_ty = &self.infer[scrutinee_expr]; let Some(scrut_ty) = self.infer.type_of_expr_with_adjust(scrutinee_expr) else {
return;
};
if scrut_ty.contains_unknown() { if scrut_ty.contains_unknown() {
return; return;
} }
@ -200,7 +203,7 @@ impl ExprValidator {
// Note: Skipping the entire diagnostic rather than just not including a faulty match arm is // Note: Skipping the entire diagnostic rather than just not including a faulty match arm is
// preferred to avoid the chance of false positives. // preferred to avoid the chance of false positives.
for arm in arms { for arm in arms {
let Some(pat_ty) = self.infer.type_of_pat.get(arm.pat) else { let Some(pat_ty) = self.infer.type_of_pat_with_adjust(arm.pat) else {
return; return;
}; };
if pat_ty.contains_unknown() { if pat_ty.contains_unknown() {
@ -328,7 +331,7 @@ impl ExprValidator {
continue; continue;
} }
let Some(initializer) = initializer else { continue }; let Some(initializer) = initializer else { continue };
let ty = &self.infer[initializer]; let Some(ty) = self.infer.type_of_expr_with_adjust(initializer) else { continue };
if ty.contains_unknown() { if ty.contains_unknown() {
continue; continue;
} }
@ -433,44 +436,44 @@ impl ExprValidator {
Statement::Expr { expr, .. } => Some(*expr), Statement::Expr { expr, .. } => Some(*expr),
_ => None, _ => None,
}); });
if let Some(last_then_expr) = last_then_expr { if let Some(last_then_expr) = last_then_expr
let last_then_expr_ty = &self.infer[last_then_expr]; && let Some(last_then_expr_ty) =
if last_then_expr_ty.is_never() { self.infer.type_of_expr_with_adjust(last_then_expr)
// Only look at sources if the then branch diverges and we have an else branch. && last_then_expr_ty.is_never()
let source_map = db.body_with_source_map(self.owner).1; {
let Ok(source_ptr) = source_map.expr_syntax(id) else { // Only look at sources if the then branch diverges and we have an else branch.
return; let source_map = db.body_with_source_map(self.owner).1;
}; let Ok(source_ptr) = source_map.expr_syntax(id) else {
let root = source_ptr.file_syntax(db); return;
let either::Left(ast::Expr::IfExpr(if_expr)) = };
source_ptr.value.to_node(&root) let root = source_ptr.file_syntax(db);
else { let either::Left(ast::Expr::IfExpr(if_expr)) = source_ptr.value.to_node(&root)
return; else {
}; return;
let mut top_if_expr = if_expr; };
loop { let mut top_if_expr = if_expr;
let parent = top_if_expr.syntax().parent(); loop {
let has_parent_expr_stmt_or_stmt_list = let parent = top_if_expr.syntax().parent();
parent.as_ref().is_some_and(|node| { let has_parent_expr_stmt_or_stmt_list =
ast::ExprStmt::can_cast(node.kind()) parent.as_ref().is_some_and(|node| {
| ast::StmtList::can_cast(node.kind()) ast::ExprStmt::can_cast(node.kind())
}); | ast::StmtList::can_cast(node.kind())
if has_parent_expr_stmt_or_stmt_list { });
// Only emit diagnostic if parent or direct ancestor is either if has_parent_expr_stmt_or_stmt_list {
// an expr stmt or a stmt list. // Only emit diagnostic if parent or direct ancestor is either
break; // an expr stmt or a stmt list.
} break;
let Some(parent_if_expr) = parent.and_then(ast::IfExpr::cast) else {
// Bail if parent is neither an if expr, an expr stmt nor a stmt list.
return;
};
// Check parent if expr.
top_if_expr = parent_if_expr;
} }
let Some(parent_if_expr) = parent.and_then(ast::IfExpr::cast) else {
self.diagnostics // Bail if parent is neither an if expr, an expr stmt nor a stmt list.
.push(BodyValidationDiagnostic::RemoveUnnecessaryElse { if_expr: id }) return;
};
// Check parent if expr.
top_if_expr = parent_if_expr;
} }
self.diagnostics
.push(BodyValidationDiagnostic::RemoveUnnecessaryElse { if_expr: id })
} }
} }
} }

View file

@ -561,6 +561,32 @@ impl InferenceResult {
ExprOrPatId::PatId(id) => self.type_of_pat.get(id), ExprOrPatId::PatId(id) => self.type_of_pat.get(id),
} }
} }
pub fn type_of_expr_with_adjust(&self, id: ExprId) -> Option<&Ty> {
match self.expr_adjustments.get(&id).and_then(|adjustments| {
adjustments
.iter()
.filter(|adj| {
// https://github.com/rust-lang/rust/blob/67819923ac8ea353aaa775303f4c3aacbf41d010/compiler/rustc_mir_build/src/thir/cx/expr.rs#L140
!matches!(
adj,
Adjustment {
kind: Adjust::NeverToAny,
target,
} if target.is_never()
)
})
.next_back()
}) {
Some(adjustment) => Some(&adjustment.target),
None => self.type_of_expr.get(id),
}
}
pub fn type_of_pat_with_adjust(&self, id: PatId) -> Option<&Ty> {
match self.pat_adjustments.get(&id).and_then(|adjustments| adjustments.last()) {
adjusted @ Some(_) => adjusted,
None => self.type_of_pat.get(id),
}
}
pub fn is_erroneous(&self) -> bool { pub fn is_erroneous(&self) -> bool {
self.has_errors && self.type_of_expr.iter().count() == 0 self.has_errors && self.type_of_expr.iter().count() == 0
} }

View file

@ -3,9 +3,9 @@
use std::{cmp, ops::Bound}; use std::{cmp, ops::Bound};
use hir_def::{ use hir_def::{
AdtId, VariantId,
layout::{Integer, ReprOptions, TargetDataLayout}, layout::{Integer, ReprOptions, TargetDataLayout},
signatures::{StructFlags, VariantFields}, signatures::{StructFlags, VariantFields},
AdtId, VariantId,
}; };
use intern::sym; use intern::sym;
use rustc_index::IndexVec; use rustc_index::IndexVec;
@ -13,9 +13,9 @@ use smallvec::SmallVec;
use triomphe::Arc; use triomphe::Arc;
use crate::{ use crate::{
db::HirDatabase,
layout::{field_ty, Layout, LayoutError},
Substitution, TraitEnvironment, Substitution, TraitEnvironment,
db::HirDatabase,
layout::{Layout, LayoutError, field_ty},
}; };
use super::LayoutCx; use super::LayoutCx;

View file

@ -590,9 +590,14 @@ impl<'a> TyLoweringContext<'a> {
.resolve_trait(ctx.ty_ctx().db, ctx.ty_ctx().resolver.krate()); .resolve_trait(ctx.ty_ctx().db, ctx.ty_ctx().resolver.krate());
let pointee_sized = LangItem::PointeeSized let pointee_sized = LangItem::PointeeSized
.resolve_trait(ctx.ty_ctx().db, ctx.ty_ctx().resolver.krate()); .resolve_trait(ctx.ty_ctx().db, ctx.ty_ctx().resolver.krate());
if meta_sized.is_some_and(|it| it == trait_ref.hir_trait_id()) { let destruct = LangItem::Destruct
.resolve_trait(ctx.ty_ctx().db, ctx.ty_ctx().resolver.krate());
let hir_trait_id = trait_ref.hir_trait_id();
if meta_sized.is_some_and(|it| it == hir_trait_id)
|| destruct.is_some_and(|it| it == hir_trait_id)
{
// Ignore this bound // Ignore this bound
} else if pointee_sized.is_some_and(|it| it == trait_ref.hir_trait_id()) { } else if pointee_sized.is_some_and(|it| it == hir_trait_id) {
// Regard this as `?Sized` bound // Regard this as `?Sized` bound
ctx.ty_ctx().unsized_types.insert(self_ty); ctx.ty_ctx().unsized_types.insert(self_ty);
} else { } else {

View file

@ -2349,3 +2349,37 @@ fn test() {
"#]], "#]],
); );
} }
#[test]
fn rust_destruct_option_clone() {
check_types(
r#"
//- minicore: option, drop
fn test(o: &Option<i32>) {
o.my_clone();
//^^^^^^^^^^^^ Option<i32>
}
pub trait MyClone: Sized {
fn my_clone(&self) -> Self;
}
impl<T> const MyClone for Option<T>
where
T: ~const MyClone + ~const Destruct,
{
fn my_clone(&self) -> Self {
match self {
Some(x) => Some(x.my_clone()),
None => None,
}
}
}
impl const MyClone for i32 {
fn my_clone(&self) -> Self {
*self
}
}
#[lang = "destruct"]
pub trait Destruct {}
"#,
);
}

View file

@ -1922,10 +1922,6 @@ impl DefWithBody {
Module { id: def_map.module_id(DefMap::ROOT) }.diagnostics(db, acc, style_lints); Module { id: def_map.module_id(DefMap::ROOT) }.diagnostics(db, acc, style_lints);
} }
source_map
.macro_calls()
.for_each(|(_ast_id, call_id)| macro_call_diagnostics(db, call_id, acc));
expr_store_diagnostics(db, acc, &source_map); expr_store_diagnostics(db, acc, &source_map);
let infer = db.infer(self.into()); let infer = db.infer(self.into());
@ -2130,9 +2126,9 @@ impl DefWithBody {
} }
} }
fn expr_store_diagnostics( fn expr_store_diagnostics<'db>(
db: &dyn HirDatabase, db: &'db dyn HirDatabase,
acc: &mut Vec<AnyDiagnostic<'_>>, acc: &mut Vec<AnyDiagnostic<'db>>,
source_map: &ExpressionStoreSourceMap, source_map: &ExpressionStoreSourceMap,
) { ) {
for diag in source_map.diagnostics() { for diag in source_map.diagnostics() {
@ -2140,30 +2136,6 @@ fn expr_store_diagnostics(
ExpressionStoreDiagnostics::InactiveCode { node, cfg, opts } => { ExpressionStoreDiagnostics::InactiveCode { node, cfg, opts } => {
InactiveCode { node: *node, cfg: cfg.clone(), opts: opts.clone() }.into() InactiveCode { node: *node, cfg: cfg.clone(), opts: opts.clone() }.into()
} }
ExpressionStoreDiagnostics::MacroError { node, err } => {
let RenderedExpandError { message, error, kind } = err.render_to_string(db);
let editioned_file_id = EditionedFileId::from_span(db, err.span().anchor.file_id);
let precise_location = if editioned_file_id == node.file_id {
Some(
err.span().range
+ db.ast_id_map(editioned_file_id.into())
.get_erased(err.span().anchor.ast_id)
.text_range()
.start(),
)
} else {
None
};
MacroError {
node: (node).map(|it| it.into()),
precise_location,
message,
error,
kind,
}
.into()
}
ExpressionStoreDiagnostics::UnresolvedMacroCall { node, path } => UnresolvedMacroCall { ExpressionStoreDiagnostics::UnresolvedMacroCall { node, path } => UnresolvedMacroCall {
macro_call: (*node).map(|ast_ptr| ast_ptr.into()), macro_call: (*node).map(|ast_ptr| ast_ptr.into()),
precise_location: None, precise_location: None,
@ -2182,6 +2154,10 @@ fn expr_store_diagnostics(
} }
}); });
} }
source_map
.macro_calls()
.for_each(|(_ast_id, call_id)| macro_call_diagnostics(db, call_id, acc));
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Function { pub struct Function {

View file

@ -441,7 +441,7 @@ impl<'db> SourceAnalyzer<'db> {
) -> Option<GenericSubstitution<'db>> { ) -> Option<GenericSubstitution<'db>> {
let body = self.store()?; let body = self.store()?;
if let Expr::Field { expr: object_expr, name: _ } = body[field_expr] { if let Expr::Field { expr: object_expr, name: _ } = body[field_expr] {
let (adt, subst) = type_of_expr_including_adjust(infer, object_expr)?.as_adt()?; let (adt, subst) = infer.type_of_expr_with_adjust(object_expr)?.as_adt()?;
return Some(GenericSubstitution::new( return Some(GenericSubstitution::new(
adt.into(), adt.into(),
subst.clone(), subst.clone(),
@ -1780,10 +1780,3 @@ pub(crate) fn name_hygiene(db: &dyn HirDatabase, name: InFile<&SyntaxNode>) -> H
let ctx = span_map.span_at(name.value.text_range().start()).ctx; let ctx = span_map.span_at(name.value.text_range().start()).ctx;
HygieneId::new(ctx.opaque_and_semitransparent(db)) HygieneId::new(ctx.opaque_and_semitransparent(db))
} }
fn type_of_expr_including_adjust(infer: &InferenceResult, id: ExprId) -> Option<&Ty> {
match infer.expr_adjustment(id).and_then(|adjustments| adjustments.last()) {
Some(adjustment) => Some(&adjustment.target),
None => Some(&infer[id]),
}
}

View file

@ -2,6 +2,7 @@ use hir::HasSource;
use syntax::{ use syntax::{
Edition, Edition,
ast::{self, AstNode, make}, ast::{self, AstNode, make},
syntax_editor::{Position, SyntaxEditor},
}; };
use crate::{ use crate::{
@ -147,45 +148,78 @@ fn add_missing_impl_members_inner(
let target = impl_def.syntax().text_range(); let target = impl_def.syntax().text_range();
acc.add(AssistId::quick_fix(assist_id), label, target, |edit| { acc.add(AssistId::quick_fix(assist_id), label, target, |edit| {
let new_impl_def = edit.make_mut(impl_def.clone()); let new_item = add_trait_assoc_items_to_impl(
let first_new_item = add_trait_assoc_items_to_impl(
&ctx.sema, &ctx.sema,
ctx.config, ctx.config,
&missing_items, &missing_items,
trait_, trait_,
&new_impl_def, &impl_def,
&target_scope, &target_scope,
); );
let Some((first_new_item, other_items)) = new_item.split_first() else {
return;
};
let mut first_new_item = if let DefaultMethods::No = mode
&& let ast::AssocItem::Fn(func) = &first_new_item
&& let Some(body) = try_gen_trait_body(
ctx,
func,
trait_ref,
&impl_def,
target_scope.krate().edition(ctx.sema.db),
)
&& let Some(func_body) = func.body()
{
let mut func_editor = SyntaxEditor::new(first_new_item.syntax().clone_subtree());
func_editor.replace(func_body.syntax(), body.syntax());
ast::AssocItem::cast(func_editor.finish().new_root().clone())
} else {
Some(first_new_item.clone())
};
let new_assoc_items = first_new_item
.clone()
.into_iter()
.chain(other_items.iter().cloned())
.map(either::Either::Right)
.collect::<Vec<_>>();
let mut editor = edit.make_editor(impl_def.syntax());
if let Some(assoc_item_list) = impl_def.assoc_item_list() {
let items = new_assoc_items.into_iter().filter_map(either::Either::right).collect();
assoc_item_list.add_items(&mut editor, items);
} else {
let assoc_item_list = make::assoc_item_list(Some(new_assoc_items)).clone_for_update();
editor.insert_all(
Position::after(impl_def.syntax()),
vec![make::tokens::whitespace(" ").into(), assoc_item_list.syntax().clone().into()],
);
first_new_item = assoc_item_list.assoc_items().next();
}
if let Some(cap) = ctx.config.snippet_cap { if let Some(cap) = ctx.config.snippet_cap {
let mut placeholder = None; let mut placeholder = None;
if let DefaultMethods::No = mode { if let DefaultMethods::No = mode {
if let ast::AssocItem::Fn(func) = &first_new_item { if let Some(ast::AssocItem::Fn(func)) = &first_new_item {
if try_gen_trait_body( if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast)
ctx, && m.syntax().text() == "todo!()"
func,
trait_ref,
&impl_def,
target_scope.krate().edition(ctx.sema.db),
)
.is_none()
{ {
if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) placeholder = Some(m);
{
if m.syntax().text() == "todo!()" {
placeholder = Some(m);
}
}
} }
} }
} }
if let Some(macro_call) = placeholder { if let Some(macro_call) = placeholder {
edit.add_placeholder_snippet(cap, macro_call); let placeholder = edit.make_placeholder_snippet(cap);
} else { editor.add_annotation(macro_call.syntax(), placeholder);
edit.add_tabstop_before(cap, first_new_item); } else if let Some(first_new_item) = first_new_item {
let tabstop = edit.make_tabstop_before(cap);
editor.add_annotation(first_new_item.syntax(), tabstop);
}; };
}; };
edit.add_file_edits(ctx.vfs_file_id(), editor);
}) })
} }
@ -195,7 +229,7 @@ fn try_gen_trait_body(
trait_ref: hir::TraitRef<'_>, trait_ref: hir::TraitRef<'_>,
impl_def: &ast::Impl, impl_def: &ast::Impl,
edition: Edition, edition: Edition,
) -> Option<()> { ) -> Option<ast::BlockExpr> {
let trait_path = make::ext::ident_path( let trait_path = make::ext::ident_path(
&trait_ref.trait_().name(ctx.db()).display(ctx.db(), edition).to_string(), &trait_ref.trait_().name(ctx.db()).display(ctx.db(), edition).to_string(),
); );
@ -322,7 +356,7 @@ impl Foo for S {
} }
#[test] #[test]
fn test_impl_def_without_braces() { fn test_impl_def_without_braces_macro() {
check_assist( check_assist(
add_missing_impl_members, add_missing_impl_members,
r#" r#"
@ -340,6 +374,33 @@ impl Foo for S {
); );
} }
#[test]
fn test_impl_def_without_braces_tabstop_first_item() {
check_assist(
add_missing_impl_members,
r#"
trait Foo {
type Output;
fn foo(&self);
}
struct S;
impl Foo for S { $0 }"#,
r#"
trait Foo {
type Output;
fn foo(&self);
}
struct S;
impl Foo for S {
$0type Output;
fn foo(&self) {
todo!()
}
}"#,
);
}
#[test] #[test]
fn fill_in_type_params_1() { fn fill_in_type_params_1() {
check_assist( check_assist(

View file

@ -228,8 +228,7 @@ pub(crate) fn convert_bool_then_to_if(acc: &mut Assists, ctx: &AssistContext<'_>
closure_body, closure_body,
Some(ast::ElseBranch::Block(make.block_expr(None, Some(none_path)))), Some(ast::ElseBranch::Block(make.block_expr(None, Some(none_path)))),
) )
.indent(mcall.indent_level()) .indent(mcall.indent_level());
.clone_for_update();
editor.replace(mcall.syntax().clone(), if_expr.syntax().clone()); editor.replace(mcall.syntax().clone(), if_expr.syntax().clone());
editor.add_mappings(make.finish_with_mappings()); editor.add_mappings(make.finish_with_mappings());

View file

@ -13,7 +13,6 @@ use syntax::{
edit::{AstNodeEdit, IndentLevel}, edit::{AstNodeEdit, IndentLevel},
make, make,
}, },
ted,
}; };
use crate::{ use crate::{
@ -117,7 +116,7 @@ fn if_expr_to_guarded_return(
then_block.syntax().last_child_or_token().filter(|t| t.kind() == T!['}'])?; then_block.syntax().last_child_or_token().filter(|t| t.kind() == T!['}'])?;
let then_block_items = then_block.dedent(IndentLevel(1)).clone_for_update(); let then_block_items = then_block.dedent(IndentLevel(1));
let end_of_then = then_block_items.syntax().last_child_or_token()?; let end_of_then = then_block_items.syntax().last_child_or_token()?;
let end_of_then = if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { let end_of_then = if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
@ -132,7 +131,6 @@ fn if_expr_to_guarded_return(
"Convert to guarded return", "Convert to guarded return",
target, target,
|edit| { |edit| {
let if_expr = edit.make_mut(if_expr);
let if_indent_level = IndentLevel::from_node(if_expr.syntax()); let if_indent_level = IndentLevel::from_node(if_expr.syntax());
let replacement = match if_let_pat { let replacement = match if_let_pat {
None => { None => {
@ -143,7 +141,7 @@ fn if_expr_to_guarded_return(
let cond = invert_boolean_expression_legacy(cond_expr); let cond = invert_boolean_expression_legacy(cond_expr);
make::expr_if(cond, then_branch, None).indent(if_indent_level) make::expr_if(cond, then_branch, None).indent(if_indent_level)
}; };
new_expr.syntax().clone_for_update() new_expr.syntax().clone()
} }
Some(pat) => { Some(pat) => {
// If-let. // If-let.
@ -154,7 +152,7 @@ fn if_expr_to_guarded_return(
ast::make::tail_only_block_expr(early_expression), ast::make::tail_only_block_expr(early_expression),
); );
let let_else_stmt = let_else_stmt.indent(if_indent_level); let let_else_stmt = let_else_stmt.indent(if_indent_level);
let_else_stmt.syntax().clone_for_update() let_else_stmt.syntax().clone()
} }
}; };
@ -168,8 +166,9 @@ fn if_expr_to_guarded_return(
.take_while(|i| *i != end_of_then), .take_while(|i| *i != end_of_then),
) )
.collect(); .collect();
let mut editor = edit.make_editor(if_expr.syntax());
ted::replace_with_many(if_expr.syntax(), then_statements) editor.replace_with_many(if_expr.syntax(), then_statements);
edit.add_file_edits(ctx.vfs_file_id(), editor);
}, },
) )
} }
@ -214,7 +213,6 @@ fn let_stmt_to_guarded_return(
"Convert to guarded return", "Convert to guarded return",
target, target,
|edit| { |edit| {
let let_stmt = edit.make_mut(let_stmt);
let let_indent_level = IndentLevel::from_node(let_stmt.syntax()); let let_indent_level = IndentLevel::from_node(let_stmt.syntax());
let replacement = { let replacement = {
@ -225,10 +223,11 @@ fn let_stmt_to_guarded_return(
ast::make::tail_only_block_expr(early_expression), ast::make::tail_only_block_expr(early_expression),
); );
let let_else_stmt = let_else_stmt.indent(let_indent_level); let let_else_stmt = let_else_stmt.indent(let_indent_level);
let_else_stmt.syntax().clone_for_update() let_else_stmt.syntax().clone()
}; };
let mut editor = edit.make_editor(let_stmt.syntax());
ted::replace(let_stmt.syntax(), replacement) editor.replace(let_stmt.syntax(), replacement);
edit.add_file_edits(ctx.vfs_file_id(), editor);
}, },
) )
} }

View file

@ -8,8 +8,7 @@ use syntax::{
AstNode, AstToken, NodeOrToken, AstNode, AstToken, NodeOrToken,
SyntaxKind::WHITESPACE, SyntaxKind::WHITESPACE,
T, T,
ast::{self, make}, ast::{self, make, syntax_factory::SyntaxFactory},
ted,
}; };
// Assist: extract_expressions_from_format_string // Assist: extract_expressions_from_format_string
@ -58,8 +57,6 @@ pub(crate) fn extract_expressions_from_format_string(
"Extract format expressions", "Extract format expressions",
tt.syntax().text_range(), tt.syntax().text_range(),
|edit| { |edit| {
let tt = edit.make_mut(tt);
// Extract existing arguments in macro // Extract existing arguments in macro
let tokens = tt.token_trees_and_tokens().collect_vec(); let tokens = tt.token_trees_and_tokens().collect_vec();
@ -131,8 +128,10 @@ pub(crate) fn extract_expressions_from_format_string(
} }
// Insert new args // Insert new args
let new_tt = make::token_tree(tt_delimiter, new_tt_bits).clone_for_update(); let make = SyntaxFactory::with_mappings();
ted::replace(tt.syntax(), new_tt.syntax()); let new_tt = make.token_tree(tt_delimiter, new_tt_bits);
let mut editor = edit.make_editor(tt.syntax());
editor.replace(tt.syntax(), new_tt.syntax());
if let Some(cap) = ctx.config.snippet_cap { if let Some(cap) = ctx.config.snippet_cap {
// Add placeholder snippets over placeholder args // Add placeholder snippets over placeholder args
@ -145,15 +144,19 @@ pub(crate) fn extract_expressions_from_format_string(
}; };
if stdx::always!(placeholder.kind() == T![_]) { if stdx::always!(placeholder.kind() == T![_]) {
edit.add_placeholder_snippet_token(cap, placeholder); let annotation = edit.make_placeholder_snippet(cap);
editor.add_annotation(placeholder, annotation);
} }
} }
// Add the final tabstop after the format literal // Add the final tabstop after the format literal
if let Some(NodeOrToken::Token(literal)) = new_tt.token_trees_and_tokens().nth(1) { if let Some(NodeOrToken::Token(literal)) = new_tt.token_trees_and_tokens().nth(1) {
edit.add_tabstop_after_token(cap, literal); let annotation = edit.make_tabstop_after(cap);
editor.add_annotation(literal, annotation);
} }
} }
editor.add_mappings(make.finish_with_mappings());
edit.add_file_edits(ctx.vfs_file_id(), editor);
}, },
); );

View file

@ -16,8 +16,9 @@ use syntax::{
SyntaxKind::*, SyntaxKind::*,
SyntaxNode, T, SyntaxNode, T,
ast::{ ast::{
self, AstNode, HasAttrs, HasGenericParams, HasName, HasVisibility, edit::IndentLevel, self, AstNode, HasAttrs, HasGenericParams, HasName, HasVisibility,
edit_in_place::Indent, make, edit::{AstNodeEdit, IndentLevel},
make,
}, },
match_ast, ted, match_ast, ted,
}; };
@ -110,20 +111,30 @@ pub(crate) fn extract_struct_from_enum_variant(
let generics = generic_params.as_ref().map(|generics| generics.clone_for_update()); let generics = generic_params.as_ref().map(|generics| generics.clone_for_update());
// resolve GenericArg in field_list to actual type // resolve GenericArg in field_list to actual type
let field_list = field_list.clone_for_update(); let field_list = if let Some((target_scope, source_scope)) =
if let Some((target_scope, source_scope)) =
ctx.sema.scope(enum_ast.syntax()).zip(ctx.sema.scope(field_list.syntax())) ctx.sema.scope(enum_ast.syntax()).zip(ctx.sema.scope(field_list.syntax()))
{ {
PathTransform::generic_transformation(&target_scope, &source_scope) let field_list = field_list.reset_indent();
.apply(field_list.syntax()); let field_list =
} PathTransform::generic_transformation(&target_scope, &source_scope)
.apply(field_list.syntax());
match_ast! {
match field_list {
ast::RecordFieldList(field_list) => Either::Left(field_list),
ast::TupleFieldList(field_list) => Either::Right(field_list),
_ => unreachable!(),
}
}
} else {
field_list.clone_for_update()
};
let def = let def =
create_struct_def(variant_name.clone(), &variant, &field_list, generics, &enum_ast); create_struct_def(variant_name.clone(), &variant, &field_list, generics, &enum_ast);
let enum_ast = variant.parent_enum(); let enum_ast = variant.parent_enum();
let indent = enum_ast.indent_level(); let indent = enum_ast.indent_level();
def.reindent_to(indent); let def = def.indent(indent);
ted::insert_all( ted::insert_all(
ted::Position::before(enum_ast.syntax()), ted::Position::before(enum_ast.syntax()),
@ -279,7 +290,7 @@ fn create_struct_def(
field_list.clone().into() field_list.clone().into()
} }
}; };
field_list.reindent_to(IndentLevel::single()); let field_list = field_list.indent(IndentLevel::single());
let strukt = make::struct_(enum_vis, name, generics, field_list).clone_for_update(); let strukt = make::struct_(enum_vis, name, generics, field_list).clone_for_update();

View file

@ -7,7 +7,9 @@ use syntax::{
NodeOrToken, SyntaxKind, SyntaxNode, T, NodeOrToken, SyntaxKind, SyntaxNode, T,
algo::ancestors_at_offset, algo::ancestors_at_offset,
ast::{ ast::{
self, AstNode, edit::IndentLevel, edit_in_place::Indent, make, self, AstNode,
edit::{AstNodeEdit, IndentLevel},
make,
syntax_factory::SyntaxFactory, syntax_factory::SyntaxFactory,
}, },
syntax_editor::Position, syntax_editor::Position,
@ -253,12 +255,11 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
// `expr_replace` is a descendant of `to_wrap`, so we just replace it with `name_expr`. // `expr_replace` is a descendant of `to_wrap`, so we just replace it with `name_expr`.
editor.replace(expr_replace, name_expr.syntax()); editor.replace(expr_replace, name_expr.syntax());
make.block_expr([new_stmt], Some(to_wrap.clone())) make.block_expr([new_stmt], Some(to_wrap.clone()))
}; }
// fixup indentation of block
.indent_with_mapping(indent_to, &make);
editor.replace(to_wrap.syntax(), block.syntax()); editor.replace(to_wrap.syntax(), block.syntax());
// fixup indentation of block
block.indent(indent_to);
} }
} }

View file

@ -114,9 +114,13 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
let source_scope = ctx.sema.scope(v.syntax()); let source_scope = ctx.sema.scope(v.syntax());
let target_scope = ctx.sema.scope(strukt.syntax()); let target_scope = ctx.sema.scope(strukt.syntax());
if let (Some(s), Some(t)) = (source_scope, target_scope) { if let (Some(s), Some(t)) = (source_scope, target_scope) {
PathTransform::generic_transformation(&t, &s).apply(v.syntax()); ast::Fn::cast(
PathTransform::generic_transformation(&t, &s).apply(v.syntax()),
)
.unwrap_or(v)
} else {
v
} }
v
} }
None => return, None => return,
}; };

View file

@ -255,7 +255,6 @@ fn generate_impl(
delegee: &Delegee, delegee: &Delegee,
edition: Edition, edition: Edition,
) -> Option<ast::Impl> { ) -> Option<ast::Impl> {
let delegate: ast::Impl;
let db = ctx.db(); let db = ctx.db();
let ast_strukt = &strukt.strukt; let ast_strukt = &strukt.strukt;
let strukt_ty = make::ty_path(make::ext::ident_path(&strukt.name.to_string())); let strukt_ty = make::ty_path(make::ext::ident_path(&strukt.name.to_string()));
@ -266,7 +265,7 @@ fn generate_impl(
let bound_def = ctx.sema.source(delegee.to_owned())?.value; let bound_def = ctx.sema.source(delegee.to_owned())?.value;
let bound_params = bound_def.generic_param_list(); let bound_params = bound_def.generic_param_list();
delegate = make::impl_trait( let delegate = make::impl_trait(
delegee.is_unsafe(db), delegee.is_unsafe(db),
bound_params.clone(), bound_params.clone(),
bound_params.map(|params| params.to_generic_args()), bound_params.map(|params| params.to_generic_args()),
@ -304,7 +303,7 @@ fn generate_impl(
let target_scope = ctx.sema.scope(strukt.strukt.syntax())?; let target_scope = ctx.sema.scope(strukt.strukt.syntax())?;
let source_scope = ctx.sema.scope(bound_def.syntax())?; let source_scope = ctx.sema.scope(bound_def.syntax())?;
let transform = PathTransform::generic_transformation(&target_scope, &source_scope); let transform = PathTransform::generic_transformation(&target_scope, &source_scope);
transform.apply(delegate.syntax()); ast::Impl::cast(transform.apply(delegate.syntax()))
} }
Delegee::Impls(trait_, old_impl) => { Delegee::Impls(trait_, old_impl) => {
let old_impl = ctx.sema.source(old_impl.to_owned())?.value; let old_impl = ctx.sema.source(old_impl.to_owned())?.value;
@ -358,20 +357,28 @@ fn generate_impl(
// 2.3) Instantiate generics with `transform_impl`, this step also // 2.3) Instantiate generics with `transform_impl`, this step also
// remove unused params. // remove unused params.
let mut trait_gen_args = old_impl.trait_()?.generic_arg_list(); let trait_gen_args = old_impl.trait_()?.generic_arg_list().and_then(|trait_args| {
if let Some(trait_args) = &mut trait_gen_args { let trait_args = &mut trait_args.clone_for_update();
*trait_args = trait_args.clone_for_update(); if let Some(new_args) = transform_impl(
transform_impl(ctx, ast_strukt, &old_impl, &transform_args, trait_args.syntax())?; ctx,
} ast_strukt,
&old_impl,
&transform_args,
trait_args.clone_subtree(),
) {
*trait_args = new_args.clone_subtree();
Some(new_args)
} else {
None
}
});
let type_gen_args = strukt_params.clone().map(|params| params.to_generic_args()); let type_gen_args = strukt_params.clone().map(|params| params.to_generic_args());
let path_type = let path_type =
make::ty(&trait_.name(db).display_no_db(edition).to_smolstr()).clone_for_update(); make::ty(&trait_.name(db).display_no_db(edition).to_smolstr()).clone_for_update();
transform_impl(ctx, ast_strukt, &old_impl, &transform_args, path_type.syntax())?; let path_type = transform_impl(ctx, ast_strukt, &old_impl, &transform_args, path_type)?;
// 3) Generate delegate trait impl // 3) Generate delegate trait impl
delegate = make::impl_trait( let delegate = make::impl_trait(
trait_.is_unsafe(db), trait_.is_unsafe(db),
trait_gen_params, trait_gen_params,
trait_gen_args, trait_gen_args,
@ -385,7 +392,6 @@ fn generate_impl(
None, None,
) )
.clone_for_update(); .clone_for_update();
// Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths // Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths
let qualified_path_type = let qualified_path_type =
make::path_from_text(&format!("<{} as {}>", field_ty, delegate.trait_()?)); make::path_from_text(&format!("<{} as {}>", field_ty, delegate.trait_()?));
@ -398,7 +404,7 @@ fn generate_impl(
.filter(|item| matches!(item, AssocItem::MacroCall(_)).not()) .filter(|item| matches!(item, AssocItem::MacroCall(_)).not())
{ {
let item = item.clone_for_update(); let item = item.clone_for_update();
transform_impl(ctx, ast_strukt, &old_impl, &transform_args, item.syntax())?; let item = transform_impl(ctx, ast_strukt, &old_impl, &transform_args, item)?;
let assoc = process_assoc_item(item, qualified_path_type.clone(), field_name)?; let assoc = process_assoc_item(item, qualified_path_type.clone(), field_name)?;
delegate_assoc_items.add_item(assoc); delegate_assoc_items.add_item(assoc);
@ -408,19 +414,18 @@ fn generate_impl(
if let Some(wc) = delegate.where_clause() { if let Some(wc) = delegate.where_clause() {
remove_useless_where_clauses(&delegate.trait_()?, &delegate.self_ty()?, wc); remove_useless_where_clauses(&delegate.trait_()?, &delegate.self_ty()?, wc);
} }
Some(delegate)
} }
} }
Some(delegate)
} }
fn transform_impl( fn transform_impl<N: ast::AstNode>(
ctx: &AssistContext<'_>, ctx: &AssistContext<'_>,
strukt: &ast::Struct, strukt: &ast::Struct,
old_impl: &ast::Impl, old_impl: &ast::Impl,
args: &Option<GenericArgList>, args: &Option<GenericArgList>,
syntax: &syntax::SyntaxNode, syntax: N,
) -> Option<()> { ) -> Option<N> {
let source_scope = ctx.sema.scope(old_impl.self_ty()?.syntax())?; let source_scope = ctx.sema.scope(old_impl.self_ty()?.syntax())?;
let target_scope = ctx.sema.scope(strukt.syntax())?; let target_scope = ctx.sema.scope(strukt.syntax())?;
let hir_old_impl = ctx.sema.to_impl_def(old_impl)?; let hir_old_impl = ctx.sema.to_impl_def(old_impl)?;
@ -437,8 +442,7 @@ fn transform_impl(
}, },
); );
transform.apply(syntax); N::cast(transform.apply(syntax.syntax()))
Some(())
} }
fn remove_instantiated_params( fn remove_instantiated_params(
@ -570,9 +574,7 @@ where
let scope = ctx.sema.scope(item.syntax())?; let scope = ctx.sema.scope(item.syntax())?;
let transform = PathTransform::adt_transformation(&scope, &scope, hir_adt, args.clone()); let transform = PathTransform::adt_transformation(&scope, &scope, hir_adt, args.clone());
transform.apply(item.syntax()); N::cast(transform.apply(item.syntax()))
Some(item)
} }
fn has_self_type(trait_: hir::Trait, ctx: &AssistContext<'_>) -> Option<()> { fn has_self_type(trait_: hir::Trait, ctx: &AssistContext<'_>) -> Option<()> {
@ -767,7 +769,7 @@ fn func_assoc_item(
) )
.clone_for_update(); .clone_for_update();
Some(AssocItem::Fn(func.indent(edit::IndentLevel(1)).clone_for_update())) Some(AssocItem::Fn(func.indent(edit::IndentLevel(1))))
} }
fn ty_assoc_item(item: syntax::ast::TypeAlias, qual_path_ty: Path) -> Option<AssocItem> { fn ty_assoc_item(item: syntax::ast::TypeAlias, qual_path_ty: Path) -> Option<AssocItem> {

View file

@ -743,17 +743,30 @@ fn fn_generic_params(
let where_preds: Vec<ast::WherePred> = let where_preds: Vec<ast::WherePred> =
where_preds.into_iter().map(|it| it.node.clone_for_update()).collect(); where_preds.into_iter().map(|it| it.node.clone_for_update()).collect();
// 4. Rewrite paths let (generic_params, where_preds): (Vec<ast::GenericParam>, Vec<ast::WherePred>) =
if let Some(param) = generic_params.first() { if let Some(param) = generic_params.first()
let source_scope = ctx.sema.scope(param.syntax())?; && let source_scope = ctx.sema.scope(param.syntax())?
let target_scope = ctx.sema.scope(&target.parent())?; && let target_scope = ctx.sema.scope(&target.parent())?
if source_scope.module() != target_scope.module() { && source_scope.module() != target_scope.module()
{
// 4. Rewrite paths
let transform = PathTransform::generic_transformation(&target_scope, &source_scope); let transform = PathTransform::generic_transformation(&target_scope, &source_scope);
let generic_params = generic_params.iter().map(|it| it.syntax()); let generic_params = generic_params.iter().map(|it| it.syntax());
let where_preds = where_preds.iter().map(|it| it.syntax()); let where_preds = where_preds.iter().map(|it| it.syntax());
transform.apply_all(generic_params.chain(where_preds)); transform
} .apply_all(generic_params.chain(where_preds))
} .into_iter()
.filter_map(|it| {
if let Some(it) = ast::GenericParam::cast(it.clone()) {
Some(either::Either::Left(it))
} else {
ast::WherePred::cast(it).map(either::Either::Right)
}
})
.partition_map(|it| it)
} else {
(generic_params, where_preds)
};
let generic_param_list = make::generic_param_list(generic_params); let generic_param_list = make::generic_param_list(generic_params);
let where_clause = let where_clause =

View file

@ -1,12 +1,17 @@
use syntax::{ use syntax::{
ast::{self, AstNode, HasName, edit_in_place::Indent, make}, ast::{self, AstNode, HasGenericParams, HasName, edit_in_place::Indent, make},
syntax_editor::{Position, SyntaxEditor}, syntax_editor::{Position, SyntaxEditor},
}; };
use crate::{AssistContext, AssistId, Assists, utils}; use crate::{
AssistContext, AssistId, Assists,
utils::{self, DefaultMethods, IgnoreAssocItems},
};
fn insert_impl(editor: &mut SyntaxEditor, impl_: &ast::Impl, nominal: &ast::Adt) { fn insert_impl(editor: &mut SyntaxEditor, impl_: &ast::Impl, nominal: &impl Indent) {
let indent = nominal.indent_level(); let indent = nominal.indent_level();
impl_.indent(indent);
editor.insert_all( editor.insert_all(
Position::after(nominal.syntax()), Position::after(nominal.syntax()),
vec![ vec![
@ -120,6 +125,126 @@ pub(crate) fn generate_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) ->
) )
} }
// Assist: generate_impl_trait
//
// Adds this trait impl for a type.
//
// ```
// trait $0Foo {
// fn foo(&self) -> i32;
// }
// ```
// ->
// ```
// trait Foo {
// fn foo(&self) -> i32;
// }
//
// impl Foo for ${1:_} {
// fn foo(&self) -> i32 {
// $0todo!()
// }
// }
// ```
pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let name = ctx.find_node_at_offset::<ast::Name>()?;
let trait_ = ast::Trait::cast(name.syntax().parent()?)?;
let target_scope = ctx.sema.scope(trait_.syntax())?;
let hir_trait = ctx.sema.to_def(&trait_)?;
let target = trait_.syntax().text_range();
acc.add(
AssistId::generate("generate_impl_trait"),
format!("Generate `{name}` impl for type"),
target,
|edit| {
let mut editor = edit.make_editor(trait_.syntax());
let holder_arg = ast::GenericArg::TypeArg(make::type_arg(make::ty_placeholder()));
let missing_items = utils::filter_assoc_items(
&ctx.sema,
&hir_trait.items(ctx.db()),
DefaultMethods::No,
IgnoreAssocItems::DocHiddenAttrPresent,
);
let trait_gen_args = trait_.generic_param_list().map(|list| {
make::generic_arg_list(list.generic_params().map(|_| holder_arg.clone()))
});
let make_impl_ = |body| {
make::impl_trait(
trait_.unsafe_token().is_some(),
None,
trait_gen_args.clone(),
None,
None,
false,
make::ty(&name.text()),
make::ty_placeholder(),
None,
None,
body,
)
.clone_for_update()
};
let impl_ = if missing_items.is_empty() {
make_impl_(None)
} else {
let impl_ = make_impl_(None);
let assoc_items = utils::add_trait_assoc_items_to_impl(
&ctx.sema,
ctx.config,
&missing_items,
hir_trait,
&impl_,
&target_scope,
);
let assoc_items = assoc_items.into_iter().map(either::Either::Right).collect();
let assoc_item_list = make::assoc_item_list(Some(assoc_items));
make_impl_(Some(assoc_item_list))
};
if let Some(cap) = ctx.config.snippet_cap {
if let Some(generics) = impl_.trait_().and_then(|it| it.generic_arg_list()) {
for generic in generics.generic_args() {
let placeholder = edit.make_placeholder_snippet(cap);
editor.add_annotation(generic.syntax(), placeholder);
}
}
if let Some(ty) = impl_.self_ty() {
let placeholder = edit.make_placeholder_snippet(cap);
editor.add_annotation(ty.syntax(), placeholder);
}
if let Some(expr) =
impl_.assoc_item_list().and_then(|it| it.assoc_items().find_map(extract_expr))
{
let tabstop = edit.make_tabstop_before(cap);
editor.add_annotation(expr.syntax(), tabstop);
} else if let Some(l_curly) =
impl_.assoc_item_list().and_then(|it| it.l_curly_token())
{
let tabstop = edit.make_tabstop_after(cap);
editor.add_annotation(l_curly, tabstop);
}
}
insert_impl(&mut editor, &impl_, &trait_);
edit.add_file_edits(ctx.vfs_file_id(), editor);
},
)
}
fn extract_expr(item: ast::AssocItem) -> Option<ast::Expr> {
let ast::AssocItem::Fn(f) = item else {
return None;
};
f.body()?.tail_expr()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::tests::{check_assist, check_assist_target}; use crate::tests::{check_assist, check_assist_target};
@ -492,4 +617,209 @@ mod tests {
"#, "#,
); );
} }
#[test]
fn test_add_impl_trait() {
check_assist(
generate_impl_trait,
r#"
trait $0Foo {
fn foo(&self) -> i32;
fn bar(&self) -> i32 {
self.foo()
}
}
"#,
r#"
trait Foo {
fn foo(&self) -> i32;
fn bar(&self) -> i32 {
self.foo()
}
}
impl Foo for ${1:_} {
fn foo(&self) -> i32 {
$0todo!()
}
}
"#,
);
}
#[test]
fn test_add_impl_trait_use_generic() {
check_assist(
generate_impl_trait,
r#"
trait $0Foo<T> {
fn foo(&self) -> T;
fn bar(&self) -> T {
self.foo()
}
}
"#,
r#"
trait Foo<T> {
fn foo(&self) -> T;
fn bar(&self) -> T {
self.foo()
}
}
impl Foo<${1:_}> for ${2:_} {
fn foo(&self) -> _ {
$0todo!()
}
}
"#,
);
check_assist(
generate_impl_trait,
r#"
trait $0Foo<T, U> {
fn foo(&self) -> T;
fn bar(&self) -> T {
self.foo()
}
}
"#,
r#"
trait Foo<T, U> {
fn foo(&self) -> T;
fn bar(&self) -> T {
self.foo()
}
}
impl Foo<${1:_}, ${2:_}> for ${3:_} {
fn foo(&self) -> _ {
$0todo!()
}
}
"#,
);
}
#[test]
fn test_add_impl_trait_docs() {
check_assist(
generate_impl_trait,
r#"
/// foo
trait $0Foo {
/// foo method
fn foo(&self) -> i32;
fn bar(&self) -> i32 {
self.foo()
}
}
"#,
r#"
/// foo
trait Foo {
/// foo method
fn foo(&self) -> i32;
fn bar(&self) -> i32 {
self.foo()
}
}
impl Foo for ${1:_} {
fn foo(&self) -> i32 {
$0todo!()
}
}
"#,
);
}
#[test]
fn test_add_impl_trait_assoc_types() {
check_assist(
generate_impl_trait,
r#"
trait $0Foo {
type Output;
fn foo(&self) -> Self::Output;
}
"#,
r#"
trait Foo {
type Output;
fn foo(&self) -> Self::Output;
}
impl Foo for ${1:_} {
type Output;
fn foo(&self) -> Self::Output {
$0todo!()
}
}
"#,
);
}
#[test]
fn test_add_impl_trait_indent() {
check_assist(
generate_impl_trait,
r#"
mod foo {
mod bar {
trait $0Foo {
type Output;
fn foo(&self) -> Self::Output;
}
}
}
"#,
r#"
mod foo {
mod bar {
trait Foo {
type Output;
fn foo(&self) -> Self::Output;
}
impl Foo for ${1:_} {
type Output;
fn foo(&self) -> Self::Output {
$0todo!()
}
}
}
}
"#,
);
}
#[test]
fn test_add_impl_trait_empty() {
check_assist(
generate_impl_trait,
r#"
trait $0Foo {}
"#,
r#"
trait Foo {}
impl Foo for ${1:_} {$0}
"#,
);
}
} }

View file

@ -94,7 +94,7 @@ pub(crate) fn generate_mut_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>
})?; })?;
let _ = process_ref_mut(&fn_); let _ = process_ref_mut(&fn_);
let assoc_list = make::assoc_item_list().clone_for_update(); let assoc_list = make::assoc_item_list(None).clone_for_update();
ted::replace(impl_def.assoc_item_list()?.syntax(), assoc_list.syntax()); ted::replace(impl_def.assoc_item_list()?.syntax(), assoc_list.syntax());
impl_def.get_or_create_assoc_item_list().add_item(syntax::ast::AssocItem::Fn(fn_)); impl_def.get_or_create_assoc_item_list().add_item(syntax::ast::AssocItem::Fn(fn_));

View file

@ -4,12 +4,12 @@ use ide_db::{
}; };
use syntax::{ use syntax::{
ast::{self, AstNode, HasName, HasVisibility, StructKind, edit_in_place::Indent, make}, ast::{self, AstNode, HasName, HasVisibility, StructKind, edit_in_place::Indent, make},
ted, syntax_editor::Position,
}; };
use crate::{ use crate::{
AssistContext, AssistId, Assists, AssistContext, AssistId, Assists,
utils::{find_struct_impl, generate_impl}, utils::{find_struct_impl, generate_impl_with_item},
}; };
// Assist: generate_new // Assist: generate_new
@ -149,7 +149,53 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
.clone_for_update(); .clone_for_update();
fn_.indent(1.into()); fn_.indent(1.into());
if let Some(cap) = ctx.config.snippet_cap { let mut editor = builder.make_editor(strukt.syntax());
// Get the node for set annotation
let contain_fn = if let Some(impl_def) = impl_def {
fn_.indent(impl_def.indent_level());
if let Some(l_curly) = impl_def.assoc_item_list().and_then(|list| list.l_curly_token())
{
editor.insert_all(
Position::after(l_curly),
vec![
make::tokens::whitespace(&format!("\n{}", impl_def.indent_level() + 1))
.into(),
fn_.syntax().clone().into(),
make::tokens::whitespace("\n").into(),
],
);
fn_.syntax().clone()
} else {
let items = vec![either::Either::Right(ast::AssocItem::Fn(fn_))];
let list = make::assoc_item_list(Some(items));
editor.insert(Position::after(impl_def.syntax()), list.syntax());
list.syntax().clone()
}
} else {
// Generate a new impl to add the method to
let indent_level = strukt.indent_level();
let body = vec![either::Either::Right(ast::AssocItem::Fn(fn_))];
let list = make::assoc_item_list(Some(body));
let impl_def = generate_impl_with_item(&ast::Adt::Struct(strukt.clone()), Some(list));
impl_def.indent(strukt.indent_level());
// Insert it after the adt
editor.insert_all(
Position::after(strukt.syntax()),
vec![
make::tokens::whitespace(&format!("\n\n{indent_level}")).into(),
impl_def.syntax().clone().into(),
],
);
impl_def.syntax().clone()
};
if let Some(fn_) = contain_fn.descendants().find_map(ast::Fn::cast)
&& let Some(cap) = ctx.config.snippet_cap
{
match strukt.kind() { match strukt.kind() {
StructKind::Tuple(_) => { StructKind::Tuple(_) => {
let struct_args = fn_ let struct_args = fn_
@ -168,8 +214,8 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
for (struct_arg, fn_param) in struct_args.zip(fn_params.params()) { for (struct_arg, fn_param) in struct_args.zip(fn_params.params()) {
if let Some(fn_pat) = fn_param.pat() { if let Some(fn_pat) = fn_param.pat() {
let fn_pat = fn_pat.syntax().clone(); let fn_pat = fn_pat.syntax().clone();
builder let placeholder = builder.make_placeholder_snippet(cap);
.add_placeholder_snippet_group(cap, vec![struct_arg, fn_pat]); editor.add_annotation_all(vec![struct_arg, fn_pat], placeholder)
} }
} }
} }
@ -179,36 +225,12 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
// Add a tabstop before the name // Add a tabstop before the name
if let Some(name) = fn_.name() { if let Some(name) = fn_.name() {
builder.add_tabstop_before(cap, name); let tabstop_before = builder.make_tabstop_before(cap);
editor.add_annotation(name.syntax(), tabstop_before);
} }
} }
// Get the mutable version of the impl to modify builder.add_file_edits(ctx.vfs_file_id(), editor);
let impl_def = if let Some(impl_def) = impl_def {
fn_.indent(impl_def.indent_level());
builder.make_mut(impl_def)
} else {
// Generate a new impl to add the method to
let impl_def = generate_impl(&ast::Adt::Struct(strukt.clone()));
let indent_level = strukt.indent_level();
fn_.indent(indent_level);
// Insert it after the adt
let strukt = builder.make_mut(strukt.clone());
ted::insert_all_raw(
ted::Position::after(strukt.syntax()),
vec![
make::tokens::whitespace(&format!("\n\n{indent_level}")).into(),
impl_def.syntax().clone().into(),
],
);
impl_def
};
// Add the `new` method at the start of the impl
impl_def.get_or_create_assoc_item_list().add_item_at_start(fn_.into());
}) })
} }

View file

@ -3,7 +3,7 @@ use ide_db::assists::AssistId;
use syntax::{ use syntax::{
AstNode, SyntaxKind, T, AstNode, SyntaxKind, T,
ast::{ ast::{
self, HasGenericParams, HasName, self, HasGenericParams, HasName, HasVisibility,
edit_in_place::{HasVisibilityEdit, Indent}, edit_in_place::{HasVisibilityEdit, Indent},
make, make,
}, },
@ -164,6 +164,12 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
/// `E0449` Trait items always share the visibility of their trait /// `E0449` Trait items always share the visibility of their trait
fn remove_items_visibility(item: &ast::AssocItem) { fn remove_items_visibility(item: &ast::AssocItem) {
if let Some(has_vis) = ast::AnyHasVisibility::cast(item.syntax().clone()) { if let Some(has_vis) = ast::AnyHasVisibility::cast(item.syntax().clone()) {
if let Some(vis) = has_vis.visibility()
&& let Some(token) = vis.syntax().next_sibling_or_token()
&& token.kind() == SyntaxKind::WHITESPACE
{
ted::remove(token);
}
has_vis.set_visibility(None); has_vis.set_visibility(None);
} }
} }
@ -333,11 +339,11 @@ impl F$0oo {
struct Foo; struct Foo;
trait NewTrait { trait NewTrait {
fn a_func() -> Option<()>; fn a_func() -> Option<()>;
} }
impl NewTrait for Foo { impl NewTrait for Foo {
fn a_func() -> Option<()> { fn a_func() -> Option<()> {
Some(()) Some(())
} }
}"#, }"#,

View file

@ -537,8 +537,13 @@ fn inline(
if let Some(generic_arg_list) = generic_arg_list.clone() { if let Some(generic_arg_list) = generic_arg_list.clone() {
if let Some((target, source)) = &sema.scope(node.syntax()).zip(sema.scope(fn_body.syntax())) if let Some((target, source)) = &sema.scope(node.syntax()).zip(sema.scope(fn_body.syntax()))
{ {
PathTransform::function_call(target, source, function, generic_arg_list) body.reindent_to(IndentLevel(0));
.apply(body.syntax()); if let Some(new_body) = ast::BlockExpr::cast(
PathTransform::function_call(target, source, function, generic_arg_list)
.apply(body.syntax()),
) {
body = new_body;
}
} }
} }

View file

@ -5,12 +5,12 @@ use syntax::{
SyntaxKind::WHITESPACE, SyntaxKind::WHITESPACE,
T, T,
ast::{self, AstNode, HasName, make}, ast::{self, AstNode, HasName, make},
ted::{self, Position}, syntax_editor::{Position, SyntaxEditor},
}; };
use crate::{ use crate::{
AssistConfig, AssistId, AssistConfig, AssistId,
assist_context::{AssistContext, Assists, SourceChangeBuilder}, assist_context::{AssistContext, Assists},
utils::{ utils::{
DefaultMethods, IgnoreAssocItems, add_trait_assoc_items_to_impl, filter_assoc_items, DefaultMethods, IgnoreAssocItems, add_trait_assoc_items_to_impl, filter_assoc_items,
gen_trait_fn_body, generate_trait_impl, gen_trait_fn_body, generate_trait_impl,
@ -126,98 +126,56 @@ fn add_assist(
let label = format!("Convert to manual `impl {replace_trait_path} for {annotated_name}`"); let label = format!("Convert to manual `impl {replace_trait_path} for {annotated_name}`");
acc.add(AssistId::refactor("replace_derive_with_manual_impl"), label, target, |builder| { acc.add(AssistId::refactor("replace_derive_with_manual_impl"), label, target, |builder| {
let insert_after = ted::Position::after(builder.make_mut(adt.clone()).syntax()); let insert_after = Position::after(adt.syntax());
let impl_is_unsafe = trait_.map(|s| s.is_unsafe(ctx.db())).unwrap_or(false); let impl_is_unsafe = trait_.map(|s| s.is_unsafe(ctx.db())).unwrap_or(false);
let impl_def_with_items = impl_def_from_trait( let impl_def = impl_def_from_trait(
&ctx.sema, &ctx.sema,
ctx.config, ctx.config,
adt, adt,
&annotated_name, &annotated_name,
trait_, trait_,
replace_trait_path, replace_trait_path,
impl_is_unsafe,
); );
update_attribute(builder, old_derives, old_tree, old_trait_path, attr);
let mut editor = builder.make_editor(attr.syntax());
update_attribute(&mut editor, old_derives, old_tree, old_trait_path, attr);
let trait_path = make::ty_path(replace_trait_path.clone()); let trait_path = make::ty_path(replace_trait_path.clone());
match (ctx.config.snippet_cap, impl_def_with_items) { let (impl_def, first_assoc_item) = if let Some(impl_def) = impl_def {
(None, None) => { (
let impl_def = generate_trait_impl(adt, trait_path); impl_def.clone(),
if impl_is_unsafe { impl_def.assoc_item_list().and_then(|list| list.assoc_items().next()),
ted::insert( )
Position::first_child_of(impl_def.syntax()), } else {
make::token(T![unsafe]), (generate_trait_impl(impl_is_unsafe, adt, trait_path), None)
); };
}
ted::insert_all( if let Some(cap) = ctx.config.snippet_cap {
insert_after, if let Some(first_assoc_item) = first_assoc_item {
vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()], if let ast::AssocItem::Fn(ref func) = first_assoc_item
); && let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast)
} && m.syntax().text() == "todo!()"
(None, Some((impl_def, _))) => {
if impl_is_unsafe {
ted::insert(
Position::first_child_of(impl_def.syntax()),
make::token(T![unsafe]),
);
}
ted::insert_all(
insert_after,
vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
);
}
(Some(cap), None) => {
let impl_def = generate_trait_impl(adt, trait_path);
if impl_is_unsafe {
ted::insert(
Position::first_child_of(impl_def.syntax()),
make::token(T![unsafe]),
);
}
if let Some(l_curly) = impl_def.assoc_item_list().and_then(|it| it.l_curly_token())
{ {
builder.add_tabstop_after_token(cap, l_curly); // Make the `todo!()` a placeholder
} builder.add_placeholder_snippet(cap, m);
} else {
ted::insert_all(
insert_after,
vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
);
}
(Some(cap), Some((impl_def, first_assoc_item))) => {
let mut added_snippet = false;
if impl_is_unsafe {
ted::insert(
Position::first_child_of(impl_def.syntax()),
make::token(T![unsafe]),
);
}
if let ast::AssocItem::Fn(ref func) = first_assoc_item {
if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) {
if m.syntax().text() == "todo!()" {
// Make the `todo!()` a placeholder
builder.add_placeholder_snippet(cap, m);
added_snippet = true;
}
}
}
if !added_snippet {
// If we haven't already added a snippet, add a tabstop before the generated function // If we haven't already added a snippet, add a tabstop before the generated function
builder.add_tabstop_before(cap, first_assoc_item); builder.add_tabstop_before(cap, first_assoc_item);
} }
} else if let Some(l_curly) =
ted::insert_all( impl_def.assoc_item_list().and_then(|it| it.l_curly_token())
insert_after, {
vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()], builder.add_tabstop_after_token(cap, l_curly);
);
} }
}; }
editor.insert_all(
insert_after,
vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
);
builder.add_file_edits(ctx.vfs_file_id(), editor);
}) })
} }
@ -228,7 +186,8 @@ fn impl_def_from_trait(
annotated_name: &ast::Name, annotated_name: &ast::Name,
trait_: Option<hir::Trait>, trait_: Option<hir::Trait>,
trait_path: &ast::Path, trait_path: &ast::Path,
) -> Option<(ast::Impl, ast::AssocItem)> { impl_is_unsafe: bool,
) -> Option<ast::Impl> {
let trait_ = trait_?; let trait_ = trait_?;
let target_scope = sema.scope(annotated_name.syntax())?; let target_scope = sema.scope(annotated_name.syntax())?;
@ -245,21 +204,43 @@ fn impl_def_from_trait(
if trait_items.is_empty() { if trait_items.is_empty() {
return None; return None;
} }
let impl_def = generate_trait_impl(adt, make::ty_path(trait_path.clone())); let impl_def = generate_trait_impl(impl_is_unsafe, adt, make::ty_path(trait_path.clone()));
let first_assoc_item = let assoc_items =
add_trait_assoc_items_to_impl(sema, config, &trait_items, trait_, &impl_def, &target_scope); add_trait_assoc_items_to_impl(sema, config, &trait_items, trait_, &impl_def, &target_scope);
let assoc_item_list = if let Some((first, other)) =
assoc_items.split_first().map(|(first, other)| (first.clone_subtree(), other))
{
let first_item = if let ast::AssocItem::Fn(ref func) = first
&& let Some(body) = gen_trait_fn_body(func, trait_path, adt, None)
&& let Some(func_body) = func.body()
{
let mut editor = SyntaxEditor::new(first.syntax().clone());
editor.replace(func_body.syntax(), body.syntax());
ast::AssocItem::cast(editor.finish().new_root().clone())
} else {
Some(first.clone())
};
let items = first_item
.into_iter()
.chain(other.iter().cloned())
.map(either::Either::Right)
.collect();
make::assoc_item_list(Some(items))
} else {
make::assoc_item_list(None)
}
.clone_for_update();
// Generate a default `impl` function body for the derived trait. let impl_def = impl_def.clone_subtree();
if let ast::AssocItem::Fn(ref func) = first_assoc_item { let mut editor = SyntaxEditor::new(impl_def.syntax().clone());
let _ = gen_trait_fn_body(func, trait_path, adt, None); editor.replace(impl_def.assoc_item_list()?.syntax(), assoc_item_list.syntax());
}; let impl_def = ast::Impl::cast(editor.finish().new_root().clone())?;
Some(impl_def)
Some((impl_def, first_assoc_item))
} }
fn update_attribute( fn update_attribute(
builder: &mut SourceChangeBuilder, editor: &mut SyntaxEditor,
old_derives: &[ast::Path], old_derives: &[ast::Path],
old_tree: &ast::TokenTree, old_tree: &ast::TokenTree,
old_trait_path: &ast::Path, old_trait_path: &ast::Path,
@ -272,8 +253,6 @@ fn update_attribute(
let has_more_derives = !new_derives.is_empty(); let has_more_derives = !new_derives.is_empty();
if has_more_derives { if has_more_derives {
let old_tree = builder.make_mut(old_tree.clone());
// Make the paths into flat lists of tokens in a vec // Make the paths into flat lists of tokens in a vec
let tt = new_derives.iter().map(|path| path.syntax().clone()).map(|node| { let tt = new_derives.iter().map(|path| path.syntax().clone()).map(|node| {
node.descendants_with_tokens() node.descendants_with_tokens()
@ -288,18 +267,17 @@ fn update_attribute(
let tt = tt.collect::<Vec<_>>(); let tt = tt.collect::<Vec<_>>();
let new_tree = make::token_tree(T!['('], tt).clone_for_update(); let new_tree = make::token_tree(T!['('], tt).clone_for_update();
ted::replace(old_tree.syntax(), new_tree.syntax()); editor.replace(old_tree.syntax(), new_tree.syntax());
} else { } else {
// Remove the attr and any trailing whitespace // Remove the attr and any trailing whitespace
let attr = builder.make_mut(attr.clone());
if let Some(line_break) = if let Some(line_break) =
attr.syntax().next_sibling_or_token().filter(|t| t.kind() == WHITESPACE) attr.syntax().next_sibling_or_token().filter(|t| t.kind() == WHITESPACE)
{ {
ted::remove(line_break) editor.delete(line_break)
} }
ted::remove(attr.syntax()) editor.delete(attr.syntax())
} }
} }

View file

@ -302,6 +302,7 @@ mod handlers {
generate_function::generate_function, generate_function::generate_function,
generate_impl::generate_impl, generate_impl::generate_impl,
generate_impl::generate_trait_impl, generate_impl::generate_trait_impl,
generate_impl::generate_impl_trait,
generate_is_empty_from_len::generate_is_empty_from_len, generate_is_empty_from_len::generate_is_empty_from_len,
generate_mut_trait_impl::generate_mut_trait_impl, generate_mut_trait_impl::generate_mut_trait_impl,
generate_new::generate_new, generate_new::generate_new,

View file

@ -1880,6 +1880,29 @@ impl<T: Clone> Ctx<T> {$0}
) )
} }
#[test]
fn doctest_generate_impl_trait() {
check_doc_test(
"generate_impl_trait",
r#####"
trait $0Foo {
fn foo(&self) -> i32;
}
"#####,
r#####"
trait Foo {
fn foo(&self) -> i32;
}
impl Foo for ${1:_} {
fn foo(&self) -> i32 {
$0todo!()
}
}
"#####,
)
}
#[test] #[test]
fn doctest_generate_is_empty_from_len() { fn doctest_generate_is_empty_from_len() {
check_doc_test( check_doc_test(

View file

@ -23,10 +23,11 @@ use syntax::{
ast::{ ast::{
self, HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace, self, HasArgList, HasAttrs, HasGenericParams, HasName, HasTypeBounds, Whitespace,
edit::{AstNodeEdit, IndentLevel}, edit::{AstNodeEdit, IndentLevel},
edit_in_place::{AttrsOwnerEdit, Indent, Removable}, edit_in_place::{AttrsOwnerEdit, Removable},
make, make,
syntax_factory::SyntaxFactory, syntax_factory::SyntaxFactory,
}, },
syntax_editor::SyntaxEditor,
ted, ted,
}; };
@ -178,6 +179,7 @@ pub fn filter_assoc_items(
/// [`filter_assoc_items()`]), clones each item for update and applies path transformation to it, /// [`filter_assoc_items()`]), clones each item for update and applies path transformation to it,
/// then inserts into `impl_`. Returns the modified `impl_` and the first associated item that got /// then inserts into `impl_`. Returns the modified `impl_` and the first associated item that got
/// inserted. /// inserted.
#[must_use]
pub fn add_trait_assoc_items_to_impl( pub fn add_trait_assoc_items_to_impl(
sema: &Semantics<'_, RootDatabase>, sema: &Semantics<'_, RootDatabase>,
config: &AssistConfig, config: &AssistConfig,
@ -185,71 +187,66 @@ pub fn add_trait_assoc_items_to_impl(
trait_: hir::Trait, trait_: hir::Trait,
impl_: &ast::Impl, impl_: &ast::Impl,
target_scope: &hir::SemanticsScope<'_>, target_scope: &hir::SemanticsScope<'_>,
) -> ast::AssocItem { ) -> Vec<ast::AssocItem> {
let new_indent_level = IndentLevel::from_node(impl_.syntax()) + 1; let new_indent_level = IndentLevel::from_node(impl_.syntax()) + 1;
let items = original_items.iter().map(|InFile { file_id, value: original_item }| { original_items
let cloned_item = { .iter()
if let Some(macro_file) = file_id.macro_file() { .map(|InFile { file_id, value: original_item }| {
let span_map = sema.db.expansion_span_map(macro_file); let mut cloned_item = {
let item_prettified = prettify_macro_expansion( if let Some(macro_file) = file_id.macro_file() {
sema.db, let span_map = sema.db.expansion_span_map(macro_file);
original_item.syntax().clone(), let item_prettified = prettify_macro_expansion(
&span_map, sema.db,
target_scope.krate().into(), original_item.syntax().clone(),
); &span_map,
if let Some(formatted) = ast::AssocItem::cast(item_prettified) { target_scope.krate().into(),
return formatted; );
} else { if let Some(formatted) = ast::AssocItem::cast(item_prettified) {
stdx::never!("formatted `AssocItem` could not be cast back to `AssocItem`"); return formatted;
} else {
stdx::never!("formatted `AssocItem` could not be cast back to `AssocItem`");
}
} }
original_item.clone_for_update()
} }
original_item.clone_for_update() .reset_indent();
};
if let Some(source_scope) = sema.scope(original_item.syntax()) { if let Some(source_scope) = sema.scope(original_item.syntax()) {
// FIXME: Paths in nested macros are not handled well. See // FIXME: Paths in nested macros are not handled well. See
// `add_missing_impl_members::paths_in_nested_macro_should_get_transformed` test. // `add_missing_impl_members::paths_in_nested_macro_should_get_transformed` test.
let transform = let transform =
PathTransform::trait_impl(target_scope, &source_scope, trait_, impl_.clone()); PathTransform::trait_impl(target_scope, &source_scope, trait_, impl_.clone());
transform.apply(cloned_item.syntax()); cloned_item = ast::AssocItem::cast(transform.apply(cloned_item.syntax())).unwrap();
}
cloned_item.remove_attrs_and_docs();
cloned_item.reindent_to(new_indent_level);
cloned_item
});
let assoc_item_list = impl_.get_or_create_assoc_item_list();
let mut first_item = None;
for item in items {
first_item.get_or_insert_with(|| item.clone());
match &item {
ast::AssocItem::Fn(fn_) if fn_.body().is_none() => {
let body = AstNodeEdit::indent(
&make::block_expr(
None,
Some(match config.expr_fill_default {
ExprFillDefaultMode::Todo => make::ext::expr_todo(),
ExprFillDefaultMode::Underscore => make::ext::expr_underscore(),
ExprFillDefaultMode::Default => make::ext::expr_todo(),
}),
),
new_indent_level,
);
ted::replace(fn_.get_or_create_body().syntax(), body.clone_for_update().syntax())
} }
ast::AssocItem::TypeAlias(type_alias) => { cloned_item.remove_attrs_and_docs();
if let Some(type_bound_list) = type_alias.type_bound_list() { cloned_item
type_bound_list.remove() })
.map(|item| {
match &item {
ast::AssocItem::Fn(fn_) if fn_.body().is_none() => {
let body = AstNodeEdit::indent(
&make::block_expr(
None,
Some(match config.expr_fill_default {
ExprFillDefaultMode::Todo => make::ext::expr_todo(),
ExprFillDefaultMode::Underscore => make::ext::expr_underscore(),
ExprFillDefaultMode::Default => make::ext::expr_todo(),
}),
),
IndentLevel::single(),
);
ted::replace(fn_.get_or_create_body().syntax(), body.syntax());
} }
ast::AssocItem::TypeAlias(type_alias) => {
if let Some(type_bound_list) = type_alias.type_bound_list() {
type_bound_list.remove()
}
}
_ => {}
} }
_ => {} AstNodeEdit::indent(&item, new_indent_level)
} })
.collect()
assoc_item_list.add_item(item)
}
first_item.unwrap()
} }
pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize { pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize {
@ -334,7 +331,7 @@ fn invert_special_case(make: &SyntaxFactory, expr: &ast::Expr) -> Option<ast::Ex
fn invert_special_case_legacy(expr: &ast::Expr) -> Option<ast::Expr> { fn invert_special_case_legacy(expr: &ast::Expr) -> Option<ast::Expr> {
match expr { match expr {
ast::Expr::BinExpr(bin) => { ast::Expr::BinExpr(bin) => {
let bin = bin.clone_for_update(); let bin = bin.clone_subtree();
let op_token = bin.op_token()?; let op_token = bin.op_token()?;
let rev_token = match op_token.kind() { let rev_token = match op_token.kind() {
T![==] => T![!=], T![==] => T![!=],
@ -350,8 +347,9 @@ fn invert_special_case_legacy(expr: &ast::Expr) -> Option<ast::Expr> {
); );
} }
}; };
ted::replace(op_token, make::token(rev_token)); let mut bin_editor = SyntaxEditor::new(bin.syntax().clone());
Some(bin.into()) bin_editor.replace(op_token, make::token(rev_token));
ast::Expr::cast(bin_editor.finish().new_root().clone())
} }
ast::Expr::MethodCallExpr(mce) => { ast::Expr::MethodCallExpr(mce) => {
let receiver = mce.receiver()?; let receiver = mce.receiver()?;
@ -664,16 +662,23 @@ fn generate_impl_text_inner(
/// Generates the corresponding `impl Type {}` including type and lifetime /// Generates the corresponding `impl Type {}` including type and lifetime
/// parameters. /// parameters.
pub(crate) fn generate_impl_with_item(
adt: &ast::Adt,
body: Option<ast::AssocItemList>,
) -> ast::Impl {
generate_impl_inner(false, adt, None, true, body)
}
pub(crate) fn generate_impl(adt: &ast::Adt) -> ast::Impl { pub(crate) fn generate_impl(adt: &ast::Adt) -> ast::Impl {
generate_impl_inner(adt, None, true) generate_impl_inner(false, adt, None, true, None)
} }
/// Generates the corresponding `impl <trait> for Type {}` including type /// Generates the corresponding `impl <trait> for Type {}` including type
/// and lifetime parameters, with `<trait>` appended to `impl`'s generic parameters' bounds. /// and lifetime parameters, with `<trait>` appended to `impl`'s generic parameters' bounds.
/// ///
/// This is useful for traits like `PartialEq`, since `impl<T> PartialEq for U<T>` often requires `T: PartialEq`. /// This is useful for traits like `PartialEq`, since `impl<T> PartialEq for U<T>` often requires `T: PartialEq`.
pub(crate) fn generate_trait_impl(adt: &ast::Adt, trait_: ast::Type) -> ast::Impl { pub(crate) fn generate_trait_impl(is_unsafe: bool, adt: &ast::Adt, trait_: ast::Type) -> ast::Impl {
generate_impl_inner(adt, Some(trait_), true) generate_impl_inner(is_unsafe, adt, Some(trait_), true, None)
} }
/// Generates the corresponding `impl <trait> for Type {}` including type /// Generates the corresponding `impl <trait> for Type {}` including type
@ -681,13 +686,15 @@ pub(crate) fn generate_trait_impl(adt: &ast::Adt, trait_: ast::Type) -> ast::Imp
/// ///
/// This is useful for traits like `From<T>`, since `impl<T> From<T> for U<T>` doesn't require `T: From<T>`. /// This is useful for traits like `From<T>`, since `impl<T> From<T> for U<T>` doesn't require `T: From<T>`.
pub(crate) fn generate_trait_impl_intransitive(adt: &ast::Adt, trait_: ast::Type) -> ast::Impl { pub(crate) fn generate_trait_impl_intransitive(adt: &ast::Adt, trait_: ast::Type) -> ast::Impl {
generate_impl_inner(adt, Some(trait_), false) generate_impl_inner(false, adt, Some(trait_), false, None)
} }
fn generate_impl_inner( fn generate_impl_inner(
is_unsafe: bool,
adt: &ast::Adt, adt: &ast::Adt,
trait_: Option<ast::Type>, trait_: Option<ast::Type>,
trait_is_transitive: bool, trait_is_transitive: bool,
body: Option<ast::AssocItemList>,
) -> ast::Impl { ) -> ast::Impl {
// Ensure lifetime params are before type & const params // Ensure lifetime params are before type & const params
let generic_params = adt.generic_param_list().map(|generic_params| { let generic_params = adt.generic_param_list().map(|generic_params| {
@ -727,7 +734,7 @@ fn generate_impl_inner(
let impl_ = match trait_ { let impl_ = match trait_ {
Some(trait_) => make::impl_trait( Some(trait_) => make::impl_trait(
false, is_unsafe,
None, None,
None, None,
generic_params, generic_params,
@ -737,9 +744,9 @@ fn generate_impl_inner(
ty, ty,
None, None,
adt.where_clause(), adt.where_clause(),
None, body,
), ),
None => make::impl_(generic_params, generic_args, ty, adt.where_clause(), None), None => make::impl_(generic_params, generic_args, ty, adt.where_clause(), body),
} }
.clone_for_update(); .clone_for_update();

View file

@ -1,10 +1,7 @@
//! This module contains functions to generate default trait impl function bodies where possible. //! This module contains functions to generate default trait impl function bodies where possible.
use hir::TraitRef; use hir::TraitRef;
use syntax::{ use syntax::ast::{self, AstNode, BinaryOp, CmpOp, HasName, LogicOp, edit::AstNodeEdit, make};
ast::{self, AstNode, BinaryOp, CmpOp, HasName, LogicOp, edit::AstNodeEdit, make},
ted,
};
/// Generate custom trait bodies without default implementation where possible. /// Generate custom trait bodies without default implementation where possible.
/// ///
@ -18,21 +15,33 @@ pub(crate) fn gen_trait_fn_body(
trait_path: &ast::Path, trait_path: &ast::Path,
adt: &ast::Adt, adt: &ast::Adt,
trait_ref: Option<TraitRef<'_>>, trait_ref: Option<TraitRef<'_>>,
) -> Option<()> { ) -> Option<ast::BlockExpr> {
let _ = func.body()?;
match trait_path.segment()?.name_ref()?.text().as_str() { match trait_path.segment()?.name_ref()?.text().as_str() {
"Clone" => gen_clone_impl(adt, func), "Clone" => {
"Debug" => gen_debug_impl(adt, func), stdx::always!(func.name().is_some_and(|name| name.text() == "clone"));
"Default" => gen_default_impl(adt, func), gen_clone_impl(adt)
"Hash" => gen_hash_impl(adt, func), }
"PartialEq" => gen_partial_eq(adt, func, trait_ref), "Debug" => gen_debug_impl(adt),
"PartialOrd" => gen_partial_ord(adt, func, trait_ref), "Default" => gen_default_impl(adt),
"Hash" => {
stdx::always!(func.name().is_some_and(|name| name.text() == "hash"));
gen_hash_impl(adt)
}
"PartialEq" => {
stdx::always!(func.name().is_some_and(|name| name.text() == "eq"));
gen_partial_eq(adt, trait_ref)
}
"PartialOrd" => {
stdx::always!(func.name().is_some_and(|name| name.text() == "partial_cmp"));
gen_partial_ord(adt, trait_ref)
}
_ => None, _ => None,
} }
} }
/// Generate a `Clone` impl based on the fields and members of the target type. /// Generate a `Clone` impl based on the fields and members of the target type.
fn gen_clone_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { fn gen_clone_impl(adt: &ast::Adt) -> Option<ast::BlockExpr> {
stdx::always!(func.name().is_some_and(|name| name.text() == "clone"));
fn gen_clone_call(target: ast::Expr) -> ast::Expr { fn gen_clone_call(target: ast::Expr) -> ast::Expr {
let method = make::name_ref("clone"); let method = make::name_ref("clone");
make::expr_method_call(target, method, make::arg_list(None)).into() make::expr_method_call(target, method, make::arg_list(None)).into()
@ -139,12 +148,11 @@ fn gen_clone_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
} }
}; };
let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1)); let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1));
ted::replace(func.body()?.syntax(), body.clone_for_update().syntax()); Some(body)
Some(())
} }
/// Generate a `Debug` impl based on the fields and members of the target type. /// Generate a `Debug` impl based on the fields and members of the target type.
fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { fn gen_debug_impl(adt: &ast::Adt) -> Option<ast::BlockExpr> {
let annotated_name = adt.name()?; let annotated_name = adt.name()?;
match adt { match adt {
// `Debug` cannot be derived for unions, so no default impl can be provided. // `Debug` cannot be derived for unions, so no default impl can be provided.
@ -248,8 +256,7 @@ fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
let body = make::block_expr(None, Some(match_expr.into())); let body = make::block_expr(None, Some(match_expr.into()));
let body = body.indent(ast::edit::IndentLevel(1)); let body = body.indent(ast::edit::IndentLevel(1));
ted::replace(func.body()?.syntax(), body.clone_for_update().syntax()); Some(body)
Some(())
} }
ast::Adt::Struct(strukt) => { ast::Adt::Struct(strukt) => {
@ -296,14 +303,13 @@ fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
let method = make::name_ref("finish"); let method = make::name_ref("finish");
let expr = make::expr_method_call(expr, method, make::arg_list(None)).into(); let expr = make::expr_method_call(expr, method, make::arg_list(None)).into();
let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1)); let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1));
ted::replace(func.body()?.syntax(), body.clone_for_update().syntax()); Some(body)
Some(())
} }
} }
} }
/// Generate a `Debug` impl based on the fields and members of the target type. /// Generate a `Debug` impl based on the fields and members of the target type.
fn gen_default_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { fn gen_default_impl(adt: &ast::Adt) -> Option<ast::BlockExpr> {
fn gen_default_call() -> Option<ast::Expr> { fn gen_default_call() -> Option<ast::Expr> {
let fn_name = make::ext::path_from_idents(["Default", "default"])?; let fn_name = make::ext::path_from_idents(["Default", "default"])?;
Some(make::expr_call(make::expr_path(fn_name), make::arg_list(None)).into()) Some(make::expr_call(make::expr_path(fn_name), make::arg_list(None)).into())
@ -342,15 +348,13 @@ fn gen_default_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
} }
}; };
let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1)); let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1));
ted::replace(func.body()?.syntax(), body.clone_for_update().syntax()); Some(body)
Some(())
} }
} }
} }
/// Generate a `Hash` impl based on the fields and members of the target type. /// Generate a `Hash` impl based on the fields and members of the target type.
fn gen_hash_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> { fn gen_hash_impl(adt: &ast::Adt) -> Option<ast::BlockExpr> {
stdx::always!(func.name().is_some_and(|name| name.text() == "hash"));
fn gen_hash_call(target: ast::Expr) -> ast::Stmt { fn gen_hash_call(target: ast::Expr) -> ast::Stmt {
let method = make::name_ref("hash"); let method = make::name_ref("hash");
let arg = make::expr_path(make::ext::ident_path("state")); let arg = make::expr_path(make::ext::ident_path("state"));
@ -400,13 +404,11 @@ fn gen_hash_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
}, },
}; };
ted::replace(func.body()?.syntax(), body.clone_for_update().syntax()); Some(body)
Some(())
} }
/// Generate a `PartialEq` impl based on the fields and members of the target type. /// Generate a `PartialEq` impl based on the fields and members of the target type.
fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef<'_>>) -> Option<()> { fn gen_partial_eq(adt: &ast::Adt, trait_ref: Option<TraitRef<'_>>) -> Option<ast::BlockExpr> {
stdx::always!(func.name().is_some_and(|name| name.text() == "eq"));
fn gen_eq_chain(expr: Option<ast::Expr>, cmp: ast::Expr) -> Option<ast::Expr> { fn gen_eq_chain(expr: Option<ast::Expr>, cmp: ast::Expr) -> Option<ast::Expr> {
match expr { match expr {
Some(expr) => Some(make::expr_bin_op(expr, BinaryOp::LogicOp(LogicOp::And), cmp)), Some(expr) => Some(make::expr_bin_op(expr, BinaryOp::LogicOp(LogicOp::And), cmp)),
@ -595,12 +597,10 @@ fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef<'_>
}, },
}; };
ted::replace(func.body()?.syntax(), body.clone_for_update().syntax()); Some(body)
Some(())
} }
fn gen_partial_ord(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef<'_>>) -> Option<()> { fn gen_partial_ord(adt: &ast::Adt, trait_ref: Option<TraitRef<'_>>) -> Option<ast::BlockExpr> {
stdx::always!(func.name().is_some_and(|name| name.text() == "partial_cmp"));
fn gen_partial_eq_match(match_target: ast::Expr) -> Option<ast::Stmt> { fn gen_partial_eq_match(match_target: ast::Expr) -> Option<ast::Stmt> {
let mut arms = vec![]; let mut arms = vec![];
@ -686,8 +686,7 @@ fn gen_partial_ord(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef<'_
}, },
}; };
ted::replace(func.body()?.syntax(), body.clone_for_update().syntax()); Some(body)
Some(())
} }
fn make_discriminant() -> Option<ast::Expr> { fn make_discriminant() -> Option<ast::Expr> {

View file

@ -276,7 +276,7 @@ fn get_transformed_assoc_item(
let assoc_item = assoc_item.clone_for_update(); let assoc_item = assoc_item.clone_for_update();
// FIXME: Paths in nested macros are not handled well. See // FIXME: Paths in nested macros are not handled well. See
// `macro_generated_assoc_item2` test. // `macro_generated_assoc_item2` test.
transform.apply(assoc_item.syntax()); let assoc_item = ast::AssocItem::cast(transform.apply(assoc_item.syntax()))?;
assoc_item.remove_attrs_and_docs(); assoc_item.remove_attrs_and_docs();
Some(assoc_item) Some(assoc_item)
} }
@ -301,7 +301,7 @@ fn get_transformed_fn(
let fn_ = fn_.clone_for_update(); let fn_ = fn_.clone_for_update();
// FIXME: Paths in nested macros are not handled well. See // FIXME: Paths in nested macros are not handled well. See
// `macro_generated_assoc_item2` test. // `macro_generated_assoc_item2` test.
transform.apply(fn_.syntax()); let fn_ = ast::Fn::cast(transform.apply(fn_.syntax()))?;
fn_.remove_attrs_and_docs(); fn_.remove_attrs_and_docs();
match async_ { match async_ {
AsyncSugaring::Desugar => { AsyncSugaring::Desugar => {

View file

@ -12,15 +12,16 @@ use span::Edition;
use syntax::{ use syntax::{
NodeOrToken, SyntaxNode, NodeOrToken, SyntaxNode,
ast::{self, AstNode, HasGenericArgs, make}, ast::{self, AstNode, HasGenericArgs, make},
ted, syntax_editor::{self, SyntaxEditor},
}; };
#[derive(Default)] #[derive(Default, Debug)]
struct AstSubsts { struct AstSubsts {
types_and_consts: Vec<TypeOrConst>, types_and_consts: Vec<TypeOrConst>,
lifetimes: Vec<ast::LifetimeArg>, lifetimes: Vec<ast::LifetimeArg>,
} }
#[derive(Debug)]
enum TypeOrConst { enum TypeOrConst {
Either(ast::TypeArg), // indistinguishable type or const param Either(ast::TypeArg), // indistinguishable type or const param
Const(ast::ConstArg), Const(ast::ConstArg),
@ -128,15 +129,18 @@ impl<'a> PathTransform<'a> {
} }
} }
pub fn apply(&self, syntax: &SyntaxNode) { #[must_use]
pub fn apply(&self, syntax: &SyntaxNode) -> SyntaxNode {
self.build_ctx().apply(syntax) self.build_ctx().apply(syntax)
} }
pub fn apply_all<'b>(&self, nodes: impl IntoIterator<Item = &'b SyntaxNode>) { #[must_use]
pub fn apply_all<'b>(
&self,
nodes: impl IntoIterator<Item = &'b SyntaxNode>,
) -> Vec<SyntaxNode> {
let ctx = self.build_ctx(); let ctx = self.build_ctx();
for node in nodes { nodes.into_iter().map(|node| ctx.apply(&node.clone())).collect()
ctx.apply(node);
}
} }
fn prettify_target_node(&self, node: SyntaxNode) -> SyntaxNode { fn prettify_target_node(&self, node: SyntaxNode) -> SyntaxNode {
@ -236,7 +240,7 @@ impl<'a> PathTransform<'a> {
Some((k.name(db).display(db, target_edition).to_string(), v.lifetime()?)) Some((k.name(db).display(db, target_edition).to_string(), v.lifetime()?))
}) })
.collect(); .collect();
let ctx = Ctx { let mut ctx = Ctx {
type_substs, type_substs,
const_substs, const_substs,
lifetime_substs, lifetime_substs,
@ -272,42 +276,75 @@ fn preorder_rev(item: &SyntaxNode) -> impl Iterator<Item = SyntaxNode> {
} }
impl Ctx<'_> { impl Ctx<'_> {
fn apply(&self, item: &SyntaxNode) { fn apply(&self, item: &SyntaxNode) -> SyntaxNode {
// `transform_path` may update a node's parent and that would break the // `transform_path` may update a node's parent and that would break the
// tree traversal. Thus all paths in the tree are collected into a vec // tree traversal. Thus all paths in the tree are collected into a vec
// so that such operation is safe. // so that such operation is safe.
let paths = preorder_rev(item).filter_map(ast::Path::cast).collect::<Vec<_>>(); let item = self.transform_path(item).clone_subtree();
for path in paths { let mut editor = SyntaxEditor::new(item.clone());
self.transform_path(path); preorder_rev(&item).filter_map(ast::Lifetime::cast).for_each(|lifetime| {
}
preorder_rev(item).filter_map(ast::Lifetime::cast).for_each(|lifetime| {
if let Some(subst) = self.lifetime_substs.get(&lifetime.syntax().text().to_string()) { if let Some(subst) = self.lifetime_substs.get(&lifetime.syntax().text().to_string()) {
ted::replace(lifetime.syntax(), subst.clone_subtree().clone_for_update().syntax()); editor
.replace(lifetime.syntax(), subst.clone_subtree().clone_for_update().syntax());
} }
}); });
editor.finish().new_root().clone()
} }
fn transform_default_values(&self, defaulted_params: Vec<DefaultedParam>) { fn transform_default_values(&mut self, defaulted_params: Vec<DefaultedParam>) {
// By now the default values are simply copied from where they are declared // By now the default values are simply copied from where they are declared
// and should be transformed. As any value is allowed to refer to previous // and should be transformed. As any value is allowed to refer to previous
// generic (both type and const) parameters, they should be all iterated left-to-right. // generic (both type and const) parameters, they should be all iterated left-to-right.
for param in defaulted_params { for param in defaulted_params {
let value = match param { let value = match &param {
Either::Left(k) => self.type_substs.get(&k).unwrap().syntax(), Either::Left(k) => self.type_substs.get(k).unwrap().syntax(),
Either::Right(k) => self.const_substs.get(&k).unwrap(), Either::Right(k) => self.const_substs.get(k).unwrap(),
}; };
// `transform_path` may update a node's parent and that would break the // `transform_path` may update a node's parent and that would break the
// tree traversal. Thus all paths in the tree are collected into a vec // tree traversal. Thus all paths in the tree are collected into a vec
// so that such operation is safe. // so that such operation is safe.
let paths = preorder_rev(value).filter_map(ast::Path::cast).collect::<Vec<_>>(); let new_value = self.transform_path(value);
for path in paths { match param {
self.transform_path(path); Either::Left(k) => {
self.type_substs.insert(k, ast::Type::cast(new_value.clone()).unwrap());
}
Either::Right(k) => {
self.const_substs.insert(k, new_value.clone());
}
} }
} }
} }
fn transform_path(&self, path: ast::Path) -> Option<()> { fn transform_path(&self, path: &SyntaxNode) -> SyntaxNode {
fn find_child_paths(root_path: &SyntaxNode) -> Vec<ast::Path> {
let mut result = Vec::new();
for child in root_path.children() {
if let Some(child_path) = ast::Path::cast(child.clone()) {
result.push(child_path);
} else {
result.extend(find_child_paths(&child));
}
}
result
}
let root_path = path.clone_subtree();
let result = find_child_paths(&root_path);
let mut editor = SyntaxEditor::new(root_path.clone());
for sub_path in result {
let new = self.transform_path(sub_path.syntax());
editor.replace(sub_path.syntax(), new);
}
let update_sub_item = editor.finish().new_root().clone().clone_subtree();
let item = find_child_paths(&update_sub_item);
let mut editor = SyntaxEditor::new(update_sub_item);
for sub_path in item {
self.transform_path_(&mut editor, &sub_path);
}
editor.finish().new_root().clone()
}
fn transform_path_(&self, editor: &mut SyntaxEditor, path: &ast::Path) -> Option<()> {
if path.qualifier().is_some() { if path.qualifier().is_some() {
return None; return None;
} }
@ -319,8 +356,7 @@ impl Ctx<'_> {
// don't try to qualify sole `self` either, they are usually locals, but are returned as modules due to namespace clashing // don't try to qualify sole `self` either, they are usually locals, but are returned as modules due to namespace clashing
return None; return None;
} }
let resolution = self.source_scope.speculative_resolve(path)?;
let resolution = self.source_scope.speculative_resolve(&path)?;
match resolution { match resolution {
hir::PathResolution::TypeParam(tp) => { hir::PathResolution::TypeParam(tp) => {
@ -360,12 +396,12 @@ impl Ctx<'_> {
let segment = make::path_segment_ty(subst.clone(), trait_ref); let segment = make::path_segment_ty(subst.clone(), trait_ref);
let qualified = make::path_from_segments(std::iter::once(segment), false); let qualified = make::path_from_segments(std::iter::once(segment), false);
ted::replace(path.syntax(), qualified.clone_for_update().syntax()); editor.replace(path.syntax(), qualified.clone_for_update().syntax());
} else if let Some(path_ty) = ast::PathType::cast(parent) { } else if let Some(path_ty) = ast::PathType::cast(parent) {
let old = path_ty.syntax(); let old = path_ty.syntax();
if old.parent().is_some() { if old.parent().is_some() {
ted::replace(old, subst.clone_subtree().clone_for_update().syntax()); editor.replace(old, subst.clone_subtree().clone_for_update().syntax());
} else { } else {
// Some `path_ty` has no parent, especially ones made for default value // Some `path_ty` has no parent, especially ones made for default value
// of type parameters. // of type parameters.
@ -377,13 +413,13 @@ impl Ctx<'_> {
} }
let start = path_ty.syntax().first_child().map(NodeOrToken::Node)?; let start = path_ty.syntax().first_child().map(NodeOrToken::Node)?;
let end = path_ty.syntax().last_child().map(NodeOrToken::Node)?; let end = path_ty.syntax().last_child().map(NodeOrToken::Node)?;
ted::replace_all( editor.replace_all(
start..=end, start..=end,
new.syntax().children().map(NodeOrToken::Node).collect::<Vec<_>>(), new.syntax().children().map(NodeOrToken::Node).collect::<Vec<_>>(),
); );
} }
} else { } else {
ted::replace( editor.replace(
path.syntax(), path.syntax(),
subst.clone_subtree().clone_for_update().syntax(), subst.clone_subtree().clone_for_update().syntax(),
); );
@ -409,17 +445,28 @@ impl Ctx<'_> {
}; };
let found_path = self.target_module.find_path(self.source_scope.db, def, cfg)?; let found_path = self.target_module.find_path(self.source_scope.db, def, cfg)?;
let res = mod_path_to_ast(&found_path, self.target_edition).clone_for_update(); let res = mod_path_to_ast(&found_path, self.target_edition).clone_for_update();
let mut res_editor = SyntaxEditor::new(res.syntax().clone_subtree());
if let Some(args) = path.segment().and_then(|it| it.generic_arg_list()) { if let Some(args) = path.segment().and_then(|it| it.generic_arg_list()) {
if let Some(segment) = res.segment() { if let Some(segment) = res.segment() {
let old = segment.get_or_create_generic_arg_list(); if let Some(old) = segment.generic_arg_list() {
ted::replace(old.syntax(), args.clone_subtree().syntax().clone_for_update()) res_editor.replace(
old.syntax(),
args.clone_subtree().syntax().clone_for_update(),
)
} else {
res_editor.insert(
syntax_editor::Position::last_child_of(segment.syntax()),
args.clone_subtree().syntax().clone_for_update(),
);
}
} }
} }
ted::replace(path.syntax(), res.syntax()) let res = res_editor.finish().new_root().clone();
editor.replace(path.syntax().clone(), res);
} }
hir::PathResolution::ConstParam(cp) => { hir::PathResolution::ConstParam(cp) => {
if let Some(subst) = self.const_substs.get(&cp) { if let Some(subst) = self.const_substs.get(&cp) {
ted::replace(path.syntax(), subst.clone_subtree().clone_for_update()); editor.replace(path.syntax(), subst.clone_subtree().clone_for_update());
} }
} }
hir::PathResolution::SelfType(imp) => { hir::PathResolution::SelfType(imp) => {
@ -456,13 +503,13 @@ impl Ctx<'_> {
mod_path_to_ast(&found_path, self.target_edition).qualifier() mod_path_to_ast(&found_path, self.target_edition).qualifier()
{ {
let res = make::path_concat(qual, path_ty.path()?).clone_for_update(); let res = make::path_concat(qual, path_ty.path()?).clone_for_update();
ted::replace(path.syntax(), res.syntax()); editor.replace(path.syntax(), res.syntax());
return Some(()); return Some(());
} }
} }
} }
ted::replace(path.syntax(), ast_ty.syntax()); editor.replace(path.syntax(), ast_ty.syntax());
} }
hir::PathResolution::Local(_) hir::PathResolution::Local(_)
| hir::PathResolution::Def(_) | hir::PathResolution::Def(_)

View file

@ -131,4 +131,28 @@ fn foo(v: Enum<()>) {
"#, "#,
); );
} }
#[test]
fn regression_20259() {
check_diagnostics(
r#"
//- minicore: deref
use core::ops::Deref;
struct Foo<T>(T);
impl<T> Deref for Foo<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
fn test(x: Foo<(i32, bool)>) {
let (_a, _b): &(i32, bool) = &x;
}
"#,
);
}
} }

View file

@ -73,11 +73,13 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> {
} }
if fn_node.body().is_some() { if fn_node.body().is_some() {
// Get the actual start of the function (excluding doc comments)
let fn_start = fn_node
.fn_token()
.map(|token| token.text_range().start())
.unwrap_or(node.text_range().start());
res.push(Fold { res.push(Fold {
range: TextRange::new( range: TextRange::new(fn_start, node.text_range().end()),
node.text_range().start(),
node.text_range().end(),
),
kind: FoldKind::Function, kind: FoldKind::Function,
}); });
continue; continue;
@ -688,4 +690,21 @@ type Foo<T, U> = foo<fold arglist><
"#, "#,
) )
} }
#[test]
fn test_fold_doc_comments_with_multiline_paramlist_function() {
check(
r#"
<fold comment>/// A very very very very very very very very very very very very very very very
/// very very very long description</fold>
<fold function>fn foo<fold arglist>(
very_long_parameter_name: u32,
another_very_long_parameter_name: u32,
third_very_long_param: u32,
)</fold> <fold block>{
todo!()
}</fold></fold>
"#,
);
}
} }

View file

@ -77,17 +77,18 @@ pub(super) fn fn_ptr_hints(
return None; return None;
} }
let parent_for_type = func let parent_for_binder = func
.syntax() .syntax()
.ancestors() .ancestors()
.skip(1) .skip(1)
.take_while(|it| matches!(it.kind(), SyntaxKind::PAREN_TYPE | SyntaxKind::FOR_TYPE)) .take_while(|it| matches!(it.kind(), SyntaxKind::PAREN_TYPE | SyntaxKind::FOR_TYPE))
.find_map(ast::ForType::cast); .find_map(ast::ForType::cast)
.and_then(|it| it.for_binder());
let param_list = func.param_list()?; let param_list = func.param_list()?;
let generic_param_list = parent_for_type.as_ref().and_then(|it| it.generic_param_list()); let generic_param_list = parent_for_binder.as_ref().and_then(|it| it.generic_param_list());
let ret_type = func.ret_type(); let ret_type = func.ret_type();
let for_kw = parent_for_type.as_ref().and_then(|it| it.for_token()); let for_kw = parent_for_binder.as_ref().and_then(|it| it.for_token());
hints_( hints_(
acc, acc,
ctx, ctx,
@ -143,15 +144,16 @@ pub(super) fn fn_path_hints(
// FIXME: Support general path types // FIXME: Support general path types
let (param_list, ret_type) = func.path().as_ref().and_then(path_as_fn)?; let (param_list, ret_type) = func.path().as_ref().and_then(path_as_fn)?;
let parent_for_type = func let parent_for_binder = func
.syntax() .syntax()
.ancestors() .ancestors()
.skip(1) .skip(1)
.take_while(|it| matches!(it.kind(), SyntaxKind::PAREN_TYPE | SyntaxKind::FOR_TYPE)) .take_while(|it| matches!(it.kind(), SyntaxKind::PAREN_TYPE | SyntaxKind::FOR_TYPE))
.find_map(ast::ForType::cast); .find_map(ast::ForType::cast)
.and_then(|it| it.for_binder());
let generic_param_list = parent_for_type.as_ref().and_then(|it| it.generic_param_list()); let generic_param_list = parent_for_binder.as_ref().and_then(|it| it.generic_param_list());
let for_kw = parent_for_type.as_ref().and_then(|it| it.for_token()); let for_kw = parent_for_binder.as_ref().and_then(|it| it.for_token());
hints_( hints_(
acc, acc,
ctx, ctx,

View file

@ -12,6 +12,7 @@ use ide_db::{
source_change::SourceChangeBuilder, source_change::SourceChangeBuilder,
}; };
use itertools::Itertools; use itertools::Itertools;
use std::fmt::Write;
use stdx::{always, never}; use stdx::{always, never};
use syntax::{AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize, ast}; use syntax::{AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize, ast};
@ -459,35 +460,22 @@ fn rename_self_to_param(
} }
fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: String) -> Option<TextEdit> { fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: String) -> Option<TextEdit> {
fn target_type_name(impl_def: &ast::Impl) -> Option<String> { let mut replacement_text = new_name;
if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { replacement_text.push_str(": ");
return Some(p.path()?.segment()?.name_ref()?.text().to_string());
} if self_param.amp_token().is_some() {
None replacement_text.push('&');
}
if let Some(lifetime) = self_param.lifetime() {
write!(replacement_text, "{lifetime} ").unwrap();
}
if self_param.amp_token().and(self_param.mut_token()).is_some() {
replacement_text.push_str("mut ");
} }
match self_param.syntax().ancestors().find_map(ast::Impl::cast) { replacement_text.push_str("Self");
Some(impl_def) => {
let type_name = target_type_name(&impl_def)?;
let mut replacement_text = new_name; Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
replacement_text.push_str(": ");
match (self_param.amp_token(), self_param.mut_token()) {
(Some(_), None) => replacement_text.push('&'),
(Some(_), Some(_)) => replacement_text.push_str("&mut "),
(_, _) => (),
};
replacement_text.push_str(type_name.as_str());
Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
}
None => {
cov_mark::hit!(rename_self_outside_of_methods);
let mut replacement_text = new_name;
replacement_text.push_str(": _");
Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -2069,7 +2057,7 @@ impl Foo {
struct Foo { i: i32 } struct Foo { i: i32 }
impl Foo { impl Foo {
fn f(foo: &mut Foo) -> i32 { fn f(foo: &mut Self) -> i32 {
foo.i foo.i
} }
} }
@ -2095,7 +2083,33 @@ impl Foo {
struct Foo { i: i32 } struct Foo { i: i32 }
impl Foo { impl Foo {
fn f(foo: Foo) -> i32 { fn f(foo: Self) -> i32 {
foo.i
}
}
"#,
);
}
#[test]
fn test_owned_self_to_parameter_with_lifetime() {
cov_mark::check!(rename_self_to_param);
check(
"foo",
r#"
struct Foo<'a> { i: &'a i32 }
impl<'a> Foo<'a> {
fn f(&'a $0self) -> i32 {
self.i
}
}
"#,
r#"
struct Foo<'a> { i: &'a i32 }
impl<'a> Foo<'a> {
fn f(foo: &'a Self) -> i32 {
foo.i foo.i
} }
} }
@ -2105,7 +2119,6 @@ impl Foo {
#[test] #[test]
fn test_self_outside_of_methods() { fn test_self_outside_of_methods() {
cov_mark::check!(rename_self_outside_of_methods);
check( check(
"foo", "foo",
r#" r#"
@ -2114,7 +2127,7 @@ fn f($0self) -> i32 {
} }
"#, "#,
r#" r#"
fn f(foo: _) -> i32 { fn f(foo: Self) -> i32 {
foo.i foo.i
} }
"#, "#,
@ -2159,7 +2172,7 @@ impl Foo {
struct Foo { i: i32 } struct Foo { i: i32 }
impl Foo { impl Foo {
fn f(foo: &Foo) -> i32 { fn f(foo: &Self) -> i32 {
let self_var = 1; let self_var = 1;
foo.i foo.i
} }

View file

@ -572,9 +572,7 @@ fn closure_expr(p: &mut Parser<'_>) -> CompletedMarker {
// test closure_binder // test closure_binder
// fn main() { for<'a> || (); } // fn main() { for<'a> || (); }
if p.at(T![for]) { if p.at(T![for]) {
let b = p.start();
types::for_binder(p); types::for_binder(p);
b.complete(p, CLOSURE_BINDER);
} }
// test const_closure // test const_closure
// fn main() { let cl = const || _ = 0; } // fn main() { let cl = const || _ = 0; }

View file

@ -13,7 +13,7 @@ pub(super) fn opt_generic_param_list(p: &mut Parser<'_>) {
// test_err generic_param_list_recover // test_err generic_param_list_recover
// fn f<T: Clone,, U:, V>() {} // fn f<T: Clone,, U:, V>() {}
fn generic_param_list(p: &mut Parser<'_>) { pub(super) fn generic_param_list(p: &mut Parser<'_>) {
assert!(p.at(T![<])); assert!(p.at(T![<]));
let m = p.start(); let m = p.start();
delimited( delimited(
@ -147,7 +147,15 @@ fn type_bound(p: &mut Parser<'_>) -> bool {
let has_paren = p.eat(T!['(']); let has_paren = p.eat(T!['(']);
match p.current() { match p.current() {
LIFETIME_IDENT => lifetime(p), LIFETIME_IDENT => lifetime(p),
T![for] => types::for_type(p, false), // test for_binder_bound
// fn foo<T: for<'a> [const] async Trait>() {}
T![for] => {
types::for_binder(p);
if path_type_bound(p).is_err() {
m.abandon(p);
return false;
}
}
// test precise_capturing // test precise_capturing
// fn captures<'a: 'a, 'b: 'b, T>() -> impl Sized + use<'b, T, Self> {} // fn captures<'a: 'a, 'b: 'b, T>() -> impl Sized + use<'b, T, Self> {}
@ -180,44 +188,8 @@ fn type_bound(p: &mut Parser<'_>) -> bool {
p.bump_any(); p.bump_any();
types::for_type(p, false) types::for_type(p, false)
} }
current => { _ => {
match current { if path_type_bound(p).is_err() {
T![?] => p.bump_any(),
T![~] => {
p.bump_any();
p.expect(T![const]);
}
T!['['] => {
p.bump_any();
p.expect(T![const]);
p.expect(T![']']);
}
// test const_trait_bound
// const fn foo(_: impl const Trait) {}
T![const] => {
p.bump_any();
}
// test async_trait_bound
// fn async_foo(_: impl async Fn(&i32)) {}
T![async] => {
p.bump_any();
}
_ => (),
}
if paths::is_use_path_start(p) {
types::path_type_bounds(p, false);
// test_err type_bounds_macro_call_recovery
// fn foo<T: T![], T: T!, T: T!{}>() -> Box<T! + T!{}> {}
if p.at(T![!]) {
let m = p.start();
p.bump(T![!]);
p.error("unexpected `!` in type path, macro calls are not allowed here");
if p.at_ts(TokenSet::new(&[T!['{'], T!['['], T!['(']])) {
items::token_tree(p);
}
m.complete(p, ERROR);
}
} else {
m.abandon(p); m.abandon(p);
return false; return false;
} }
@ -231,6 +203,43 @@ fn type_bound(p: &mut Parser<'_>) -> bool {
true true
} }
fn path_type_bound(p: &mut Parser<'_>) -> Result<(), ()> {
if p.eat(T![~]) {
p.expect(T![const]);
} else if p.eat(T!['[']) {
// test maybe_const_trait_bound
// const fn foo(_: impl [const] Trait) {}
p.expect(T![const]);
p.expect(T![']']);
} else {
// test const_trait_bound
// const fn foo(_: impl const Trait) {}
p.eat(T![const]);
}
// test async_trait_bound
// fn async_foo(_: impl async Fn(&i32)) {}
p.eat(T![async]);
p.eat(T![?]);
if paths::is_use_path_start(p) {
types::path_type_bounds(p, false);
// test_err type_bounds_macro_call_recovery
// fn foo<T: T![], T: T!, T: T!{}>() -> Box<T! + T!{}> {}
if p.at(T![!]) {
let m = p.start();
p.bump(T![!]);
p.error("unexpected `!` in type path, macro calls are not allowed here");
if p.at_ts(TokenSet::new(&[T!['{'], T!['['], T!['(']])) {
items::token_tree(p);
}
m.complete(p, ERROR);
}
Ok(())
} else {
Err(())
}
}
// test where_clause // test where_clause
// fn foo() // fn foo()
// where // where

View file

@ -249,13 +249,14 @@ fn fn_ptr_type(p: &mut Parser<'_>) {
} }
pub(super) fn for_binder(p: &mut Parser<'_>) { pub(super) fn for_binder(p: &mut Parser<'_>) {
assert!(p.at(T![for])); let m = p.start();
p.bump(T![for]); p.bump(T![for]);
if p.at(T![<]) { if p.at(T![<]) {
generic_params::opt_generic_param_list(p); generic_params::generic_param_list(p);
} else { } else {
p.error("expected `<`"); p.error("expected `<`");
} }
m.complete(p, FOR_BINDER);
} }
// test for_type // test for_type

View file

@ -185,7 +185,6 @@ pub enum SyntaxKind {
BREAK_EXPR, BREAK_EXPR,
CALL_EXPR, CALL_EXPR,
CAST_EXPR, CAST_EXPR,
CLOSURE_BINDER,
CLOSURE_EXPR, CLOSURE_EXPR,
CONST, CONST,
CONST_ARG, CONST_ARG,
@ -203,6 +202,7 @@ pub enum SyntaxKind {
FN_PTR_TYPE, FN_PTR_TYPE,
FORMAT_ARGS_ARG, FORMAT_ARGS_ARG,
FORMAT_ARGS_EXPR, FORMAT_ARGS_EXPR,
FOR_BINDER,
FOR_EXPR, FOR_EXPR,
FOR_TYPE, FOR_TYPE,
GENERIC_ARG_LIST, GENERIC_ARG_LIST,
@ -358,7 +358,6 @@ impl SyntaxKind {
| BREAK_EXPR | BREAK_EXPR
| CALL_EXPR | CALL_EXPR
| CAST_EXPR | CAST_EXPR
| CLOSURE_BINDER
| CLOSURE_EXPR | CLOSURE_EXPR
| CONST | CONST
| CONST_ARG | CONST_ARG
@ -376,6 +375,7 @@ impl SyntaxKind {
| FN_PTR_TYPE | FN_PTR_TYPE
| FORMAT_ARGS_ARG | FORMAT_ARGS_ARG
| FORMAT_ARGS_EXPR | FORMAT_ARGS_EXPR
| FOR_BINDER
| FOR_EXPR | FOR_EXPR
| FOR_TYPE | FOR_TYPE
| GENERIC_ARG_LIST | GENERIC_ARG_LIST

View file

@ -253,6 +253,10 @@ mod ok {
run_and_expect_no_errors("test_data/parser/inline/ok/fn_pointer_unnamed_arg.rs"); run_and_expect_no_errors("test_data/parser/inline/ok/fn_pointer_unnamed_arg.rs");
} }
#[test] #[test]
fn for_binder_bound() {
run_and_expect_no_errors("test_data/parser/inline/ok/for_binder_bound.rs");
}
#[test]
fn for_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/for_expr.rs"); } fn for_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/for_expr.rs"); }
#[test] #[test]
fn for_range_from() { fn for_range_from() {
@ -402,6 +406,10 @@ mod ok {
#[test] #[test]
fn match_guard() { run_and_expect_no_errors("test_data/parser/inline/ok/match_guard.rs"); } fn match_guard() { run_and_expect_no_errors("test_data/parser/inline/ok/match_guard.rs"); }
#[test] #[test]
fn maybe_const_trait_bound() {
run_and_expect_no_errors("test_data/parser/inline/ok/maybe_const_trait_bound.rs");
}
#[test]
fn metas() { run_and_expect_no_errors("test_data/parser/inline/ok/metas.rs"); } fn metas() { run_and_expect_no_errors("test_data/parser/inline/ok/metas.rs"); }
#[test] #[test]
fn method_call_expr() { fn method_call_expr() {

View file

@ -37,7 +37,7 @@ SOURCE_FILE
WHITESPACE " " WHITESPACE " "
TYPE_BOUND TYPE_BOUND
L_PAREN "(" L_PAREN "("
FOR_TYPE FOR_BINDER
FOR_KW "for" FOR_KW "for"
GENERIC_PARAM_LIST GENERIC_PARAM_LIST
L_ANGLE "<" L_ANGLE "<"
@ -45,18 +45,18 @@ SOURCE_FILE
LIFETIME LIFETIME
LIFETIME_IDENT "'a" LIFETIME_IDENT "'a"
R_ANGLE ">" R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
PATH_TYPE PATH_TYPE
PATH PATH
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF
IDENT "Trait" IDENT "Trait"
GENERIC_ARG_LIST GENERIC_ARG_LIST
L_ANGLE "<" L_ANGLE "<"
LIFETIME_ARG LIFETIME_ARG
LIFETIME LIFETIME
LIFETIME_IDENT "'a" LIFETIME_IDENT "'a"
R_ANGLE ">" R_ANGLE ">"
R_PAREN ")" R_PAREN ")"
R_ANGLE ">" R_ANGLE ">"
PARAM_LIST PARAM_LIST
@ -124,7 +124,7 @@ SOURCE_FILE
WHITESPACE " " WHITESPACE " "
TYPE_BOUND TYPE_BOUND
L_PAREN "(" L_PAREN "("
FOR_TYPE FOR_BINDER
FOR_KW "for" FOR_KW "for"
GENERIC_PARAM_LIST GENERIC_PARAM_LIST
L_ANGLE "<" L_ANGLE "<"
@ -132,18 +132,18 @@ SOURCE_FILE
LIFETIME LIFETIME
LIFETIME_IDENT "'a" LIFETIME_IDENT "'a"
R_ANGLE ">" R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
PATH_TYPE PATH_TYPE
PATH PATH
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF
IDENT "Trait" IDENT "Trait"
GENERIC_ARG_LIST GENERIC_ARG_LIST
L_ANGLE "<" L_ANGLE "<"
LIFETIME_ARG LIFETIME_ARG
LIFETIME LIFETIME
LIFETIME_IDENT "'a" LIFETIME_IDENT "'a"
R_ANGLE ">" R_ANGLE ">"
R_PAREN ")" R_PAREN ")"
ERROR ERROR
R_ANGLE ">" R_ANGLE ">"
@ -186,7 +186,7 @@ SOURCE_FILE
TUPLE_EXPR TUPLE_EXPR
L_PAREN "(" L_PAREN "("
CLOSURE_EXPR CLOSURE_EXPR
CLOSURE_BINDER FOR_BINDER
FOR_KW "for" FOR_KW "for"
GENERIC_PARAM_LIST GENERIC_PARAM_LIST
L_ANGLE "<" L_ANGLE "<"
@ -243,13 +243,14 @@ SOURCE_FILE
PAREN_TYPE PAREN_TYPE
L_PAREN "(" L_PAREN "("
FOR_TYPE FOR_TYPE
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST FOR_KW "for"
L_ANGLE "<" GENERIC_PARAM_LIST
LIFETIME_PARAM L_ANGLE "<"
LIFETIME LIFETIME_PARAM
LIFETIME_IDENT "'a" LIFETIME
R_ANGLE ">" LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
PATH_TYPE PATH_TYPE
PATH PATH

View file

@ -12,13 +12,14 @@ SOURCE_FILE
WHERE_KW "where" WHERE_KW "where"
WHITESPACE " " WHITESPACE " "
WHERE_PRED WHERE_PRED
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST FOR_KW "for"
L_ANGLE "<" GENERIC_PARAM_LIST
LIFETIME_PARAM L_ANGLE "<"
LIFETIME LIFETIME_PARAM
LIFETIME_IDENT "'a" LIFETIME
R_ANGLE ">" LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE "\n" WHITESPACE "\n"
BLOCK_EXPR BLOCK_EXPR
STMT_LIST STMT_LIST

View file

@ -8,13 +8,14 @@ SOURCE_FILE
EQ "=" EQ "="
WHITESPACE " " WHITESPACE " "
FOR_TYPE FOR_TYPE
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST FOR_KW "for"
L_ANGLE "<" GENERIC_PARAM_LIST
LIFETIME_PARAM L_ANGLE "<"
LIFETIME LIFETIME_PARAM
LIFETIME_IDENT "'a" LIFETIME
R_ANGLE ">" LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
REF_TYPE REF_TYPE
AMP "&" AMP "&"
@ -37,13 +38,14 @@ SOURCE_FILE
EQ "=" EQ "="
WHITESPACE " " WHITESPACE " "
FOR_TYPE FOR_TYPE
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST FOR_KW "for"
L_ANGLE "<" GENERIC_PARAM_LIST
LIFETIME_PARAM L_ANGLE "<"
LIFETIME LIFETIME_PARAM
LIFETIME_IDENT "'a" LIFETIME
R_ANGLE ">" LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
TUPLE_TYPE TUPLE_TYPE
L_PAREN "(" L_PAREN "("
@ -70,13 +72,14 @@ SOURCE_FILE
EQ "=" EQ "="
WHITESPACE " " WHITESPACE " "
FOR_TYPE FOR_TYPE
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST FOR_KW "for"
L_ANGLE "<" GENERIC_PARAM_LIST
LIFETIME_PARAM L_ANGLE "<"
LIFETIME LIFETIME_PARAM
LIFETIME_IDENT "'a" LIFETIME
R_ANGLE ">" LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
SLICE_TYPE SLICE_TYPE
L_BRACK "[" L_BRACK "["
@ -97,22 +100,24 @@ SOURCE_FILE
EQ "=" EQ "="
WHITESPACE " " WHITESPACE " "
FOR_TYPE FOR_TYPE
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST
L_ANGLE "<"
LIFETIME_PARAM
LIFETIME
LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " "
FOR_TYPE
FOR_KW "for" FOR_KW "for"
GENERIC_PARAM_LIST GENERIC_PARAM_LIST
L_ANGLE "<" L_ANGLE "<"
LIFETIME_PARAM LIFETIME_PARAM
LIFETIME LIFETIME
LIFETIME_IDENT "'b" LIFETIME_IDENT "'a"
R_ANGLE ">" R_ANGLE ">"
WHITESPACE " "
FOR_TYPE
FOR_BINDER
FOR_KW "for"
GENERIC_PARAM_LIST
L_ANGLE "<"
LIFETIME_PARAM
LIFETIME
LIFETIME_IDENT "'b"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
FN_PTR_TYPE FN_PTR_TYPE
FN_KW "fn" FN_KW "fn"
@ -164,31 +169,34 @@ SOURCE_FILE
WHERE_KW "where" WHERE_KW "where"
WHITESPACE "\n " WHITESPACE "\n "
WHERE_PRED WHERE_PRED
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST
L_ANGLE "<"
LIFETIME_PARAM
LIFETIME
LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " "
FOR_TYPE
FOR_KW "for" FOR_KW "for"
GENERIC_PARAM_LIST GENERIC_PARAM_LIST
L_ANGLE "<" L_ANGLE "<"
LIFETIME_PARAM LIFETIME_PARAM
LIFETIME LIFETIME
LIFETIME_IDENT "'b" LIFETIME_IDENT "'a"
R_ANGLE ">" R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
FOR_TYPE FOR_TYPE
FOR_BINDER
FOR_KW "for" FOR_KW "for"
GENERIC_PARAM_LIST GENERIC_PARAM_LIST
L_ANGLE "<" L_ANGLE "<"
LIFETIME_PARAM LIFETIME_PARAM
LIFETIME LIFETIME
LIFETIME_IDENT "'c" LIFETIME_IDENT "'b"
R_ANGLE ">" R_ANGLE ">"
WHITESPACE " "
FOR_TYPE
FOR_BINDER
FOR_KW "for"
GENERIC_PARAM_LIST
L_ANGLE "<"
LIFETIME_PARAM
LIFETIME
LIFETIME_IDENT "'c"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
FN_PTR_TYPE FN_PTR_TYPE
FN_KW "fn" FN_KW "fn"

View file

@ -14,7 +14,7 @@ SOURCE_FILE
WHITESPACE " " WHITESPACE " "
EXPR_STMT EXPR_STMT
CLOSURE_EXPR CLOSURE_EXPR
CLOSURE_BINDER FOR_BINDER
FOR_KW "for" FOR_KW "for"
GENERIC_PARAM_LIST GENERIC_PARAM_LIST
L_ANGLE "<" L_ANGLE "<"

View file

@ -103,7 +103,7 @@ SOURCE_FILE
WHITESPACE " " WHITESPACE " "
TYPE_BOUND_LIST TYPE_BOUND_LIST
TYPE_BOUND TYPE_BOUND
FOR_TYPE FOR_BINDER
FOR_KW "for" FOR_KW "for"
GENERIC_PARAM_LIST GENERIC_PARAM_LIST
L_ANGLE "<" L_ANGLE "<"
@ -111,12 +111,12 @@ SOURCE_FILE
LIFETIME LIFETIME
LIFETIME_IDENT "'a" LIFETIME_IDENT "'a"
R_ANGLE ">" R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
PATH_TYPE PATH_TYPE
PATH PATH
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF
IDENT "Path" IDENT "Path"
SEMICOLON ";" SEMICOLON ";"
WHITESPACE "\n" WHITESPACE "\n"
TYPE_ALIAS TYPE_ALIAS

View file

@ -0,0 +1,45 @@
SOURCE_FILE
FN
FN_KW "fn"
WHITESPACE " "
NAME
IDENT "foo"
GENERIC_PARAM_LIST
L_ANGLE "<"
TYPE_PARAM
NAME
IDENT "T"
COLON ":"
WHITESPACE " "
TYPE_BOUND_LIST
TYPE_BOUND
FOR_BINDER
FOR_KW "for"
GENERIC_PARAM_LIST
L_ANGLE "<"
LIFETIME_PARAM
LIFETIME
LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " "
L_BRACK "["
CONST_KW "const"
R_BRACK "]"
WHITESPACE " "
ASYNC_KW "async"
WHITESPACE " "
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "Trait"
R_ANGLE ">"
PARAM_LIST
L_PAREN "("
R_PAREN ")"
WHITESPACE " "
BLOCK_EXPR
STMT_LIST
L_CURLY "{"
R_CURLY "}"
WHITESPACE "\n"

View file

@ -0,0 +1 @@
fn foo<T: for<'a> [const] async Trait>() {}

View file

@ -8,13 +8,14 @@ SOURCE_FILE
EQ "=" EQ "="
WHITESPACE " " WHITESPACE " "
FOR_TYPE FOR_TYPE
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST FOR_KW "for"
L_ANGLE "<" GENERIC_PARAM_LIST
LIFETIME_PARAM L_ANGLE "<"
LIFETIME LIFETIME_PARAM
LIFETIME_IDENT "'a" LIFETIME
R_ANGLE ">" LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
FN_PTR_TYPE FN_PTR_TYPE
FN_KW "fn" FN_KW "fn"
@ -39,13 +40,14 @@ SOURCE_FILE
EQ "=" EQ "="
WHITESPACE " " WHITESPACE " "
FOR_TYPE FOR_TYPE
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST FOR_KW "for"
L_ANGLE "<" GENERIC_PARAM_LIST
LIFETIME_PARAM L_ANGLE "<"
LIFETIME LIFETIME_PARAM
LIFETIME_IDENT "'a" LIFETIME
R_ANGLE ">" LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
FN_PTR_TYPE FN_PTR_TYPE
UNSAFE_KW "unsafe" UNSAFE_KW "unsafe"
@ -86,13 +88,14 @@ SOURCE_FILE
EQ "=" EQ "="
WHITESPACE " " WHITESPACE " "
FOR_TYPE FOR_TYPE
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST FOR_KW "for"
L_ANGLE "<" GENERIC_PARAM_LIST
LIFETIME_PARAM L_ANGLE "<"
LIFETIME LIFETIME_PARAM
LIFETIME_IDENT "'a" LIFETIME
R_ANGLE ">" LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
PATH_TYPE PATH_TYPE
PATH PATH

View file

@ -202,7 +202,7 @@ SOURCE_FILE
WHITESPACE "\n " WHITESPACE "\n "
EXPR_STMT EXPR_STMT
CLOSURE_EXPR CLOSURE_EXPR
CLOSURE_BINDER FOR_BINDER
FOR_KW "for" FOR_KW "for"
GENERIC_PARAM_LIST GENERIC_PARAM_LIST
L_ANGLE "<" L_ANGLE "<"
@ -223,7 +223,7 @@ SOURCE_FILE
WHITESPACE "\n " WHITESPACE "\n "
EXPR_STMT EXPR_STMT
CLOSURE_EXPR CLOSURE_EXPR
CLOSURE_BINDER FOR_BINDER
FOR_KW "for" FOR_KW "for"
GENERIC_PARAM_LIST GENERIC_PARAM_LIST
L_ANGLE "<" L_ANGLE "<"

View file

@ -0,0 +1,36 @@
SOURCE_FILE
FN
CONST_KW "const"
WHITESPACE " "
FN_KW "fn"
WHITESPACE " "
NAME
IDENT "foo"
PARAM_LIST
L_PAREN "("
PARAM
WILDCARD_PAT
UNDERSCORE "_"
COLON ":"
WHITESPACE " "
IMPL_TRAIT_TYPE
IMPL_KW "impl"
WHITESPACE " "
TYPE_BOUND_LIST
TYPE_BOUND
L_BRACK "["
CONST_KW "const"
R_BRACK "]"
WHITESPACE " "
PATH_TYPE
PATH
PATH_SEGMENT
NAME_REF
IDENT "Trait"
R_PAREN ")"
WHITESPACE " "
BLOCK_EXPR
STMT_LIST
L_CURLY "{"
R_CURLY "}"
WHITESPACE "\n"

View file

@ -0,0 +1 @@
const fn foo(_: impl [const] Trait) {}

View file

@ -11,13 +11,14 @@ SOURCE_FILE
TYPE_BOUND_LIST TYPE_BOUND_LIST
TYPE_BOUND TYPE_BOUND
FOR_TYPE FOR_TYPE
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST FOR_KW "for"
L_ANGLE "<" GENERIC_PARAM_LIST
LIFETIME_PARAM L_ANGLE "<"
LIFETIME LIFETIME_PARAM
LIFETIME_IDENT "'a" LIFETIME
R_ANGLE ">" LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
PATH_TYPE PATH_TYPE
PATH PATH

View file

@ -29,10 +29,11 @@ SOURCE_FILE
TYPE_BOUND TYPE_BOUND
QUESTION "?" QUESTION "?"
FOR_TYPE FOR_TYPE
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST FOR_KW "for"
L_ANGLE "<" GENERIC_PARAM_LIST
R_ANGLE ">" L_ANGLE "<"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
PATH_TYPE PATH_TYPE
PATH PATH

View file

@ -18,13 +18,14 @@ SOURCE_FILE
WHERE_KW "where" WHERE_KW "where"
WHITESPACE "\n " WHITESPACE "\n "
WHERE_PRED WHERE_PRED
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST FOR_KW "for"
L_ANGLE "<" GENERIC_PARAM_LIST
LIFETIME_PARAM L_ANGLE "<"
LIFETIME LIFETIME_PARAM
LIFETIME_IDENT "'a" LIFETIME
R_ANGLE ">" LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
PATH_TYPE PATH_TYPE
PATH PATH

View file

@ -36,7 +36,7 @@ SOURCE_FILE
PLUS "+" PLUS "+"
WHITESPACE " " WHITESPACE " "
TYPE_BOUND TYPE_BOUND
FOR_TYPE FOR_BINDER
FOR_KW "for" FOR_KW "for"
GENERIC_PARAM_LIST GENERIC_PARAM_LIST
L_ANGLE "<" L_ANGLE "<"
@ -44,18 +44,18 @@ SOURCE_FILE
LIFETIME LIFETIME
LIFETIME_IDENT "'de" LIFETIME_IDENT "'de"
R_ANGLE ">" R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
PATH_TYPE PATH_TYPE
PATH PATH
PATH_SEGMENT PATH_SEGMENT
NAME_REF NAME_REF
IDENT "Deserialize" IDENT "Deserialize"
GENERIC_ARG_LIST GENERIC_ARG_LIST
L_ANGLE "<" L_ANGLE "<"
LIFETIME_ARG LIFETIME_ARG
LIFETIME LIFETIME
LIFETIME_IDENT "'de" LIFETIME_IDENT "'de"
R_ANGLE ">" R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
PLUS "+" PLUS "+"
WHITESPACE " " WHITESPACE " "

View file

@ -18,13 +18,14 @@ SOURCE_FILE
WHERE_KW "where" WHERE_KW "where"
WHITESPACE "\n " WHITESPACE "\n "
WHERE_PRED WHERE_PRED
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST FOR_KW "for"
L_ANGLE "<" GENERIC_PARAM_LIST
LIFETIME_PARAM L_ANGLE "<"
LIFETIME LIFETIME_PARAM
LIFETIME_IDENT "'a" LIFETIME
R_ANGLE ">" LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
PATH_TYPE PATH_TYPE
PATH PATH
@ -81,13 +82,14 @@ SOURCE_FILE
WHERE_KW "where" WHERE_KW "where"
WHITESPACE "\n " WHITESPACE "\n "
WHERE_PRED WHERE_PRED
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST FOR_KW "for"
L_ANGLE "<" GENERIC_PARAM_LIST
LIFETIME_PARAM L_ANGLE "<"
LIFETIME LIFETIME_PARAM
LIFETIME_IDENT "'a" LIFETIME
R_ANGLE ">" LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
REF_TYPE REF_TYPE
AMP "&" AMP "&"
@ -135,13 +137,14 @@ SOURCE_FILE
WHERE_KW "where" WHERE_KW "where"
WHITESPACE "\n " WHITESPACE "\n "
WHERE_PRED WHERE_PRED
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST FOR_KW "for"
L_ANGLE "<" GENERIC_PARAM_LIST
LIFETIME_PARAM L_ANGLE "<"
LIFETIME LIFETIME_PARAM
LIFETIME_IDENT "'a" LIFETIME
R_ANGLE ">" LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
PAREN_TYPE PAREN_TYPE
L_PAREN "(" L_PAREN "("
@ -206,13 +209,14 @@ SOURCE_FILE
WHERE_KW "where" WHERE_KW "where"
WHITESPACE "\n " WHITESPACE "\n "
WHERE_PRED WHERE_PRED
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST FOR_KW "for"
L_ANGLE "<" GENERIC_PARAM_LIST
LIFETIME_PARAM L_ANGLE "<"
LIFETIME LIFETIME_PARAM
LIFETIME_IDENT "'a" LIFETIME
R_ANGLE ">" LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
SLICE_TYPE SLICE_TYPE
L_BRACK "[" L_BRACK "["
@ -276,13 +280,14 @@ SOURCE_FILE
WHERE_KW "where" WHERE_KW "where"
WHITESPACE "\n " WHITESPACE "\n "
WHERE_PRED WHERE_PRED
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST FOR_KW "for"
L_ANGLE "<" GENERIC_PARAM_LIST
LIFETIME_PARAM L_ANGLE "<"
LIFETIME LIFETIME_PARAM
LIFETIME_IDENT "'a" LIFETIME
R_ANGLE ">" LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
PATH_TYPE PATH_TYPE
PATH PATH
@ -349,22 +354,24 @@ SOURCE_FILE
WHERE_KW "where" WHERE_KW "where"
WHITESPACE "\n " WHITESPACE "\n "
WHERE_PRED WHERE_PRED
FOR_KW "for" FOR_BINDER
GENERIC_PARAM_LIST
L_ANGLE "<"
LIFETIME_PARAM
LIFETIME
LIFETIME_IDENT "'a"
R_ANGLE ">"
WHITESPACE " "
FOR_TYPE
FOR_KW "for" FOR_KW "for"
GENERIC_PARAM_LIST GENERIC_PARAM_LIST
L_ANGLE "<" L_ANGLE "<"
LIFETIME_PARAM LIFETIME_PARAM
LIFETIME LIFETIME
LIFETIME_IDENT "'b" LIFETIME_IDENT "'a"
R_ANGLE ">" R_ANGLE ">"
WHITESPACE " "
FOR_TYPE
FOR_BINDER
FOR_KW "for"
GENERIC_PARAM_LIST
L_ANGLE "<"
LIFETIME_PARAM
LIFETIME
LIFETIME_IDENT "'b"
R_ANGLE ">"
WHITESPACE " " WHITESPACE " "
FN_PTR_TYPE FN_PTR_TYPE
FN_KW "fn" FN_KW "fn"

View file

@ -20,6 +20,7 @@ semver.workspace = true
serde_json.workspace = true serde_json.workspace = true
serde.workspace = true serde.workspace = true
serde_derive.workspace = true serde_derive.workspace = true
temp-dir.workspace = true
tracing.workspace = true tracing.workspace = true
triomphe.workspace = true triomphe.workspace = true
la-arena.workspace = true la-arena.workspace = true

View file

@ -16,11 +16,13 @@ use la_arena::ArenaMap;
use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use serde::Deserialize as _; use serde::Deserialize as _;
use stdx::never;
use toolchain::Tool; use toolchain::Tool;
use crate::{ use crate::{
CargoConfig, CargoFeatures, CargoWorkspace, InvocationStrategy, ManifestPath, Package, Sysroot, CargoConfig, CargoFeatures, CargoWorkspace, InvocationStrategy, ManifestPath, Package, Sysroot,
TargetKind, utf8_stdout, TargetKind, cargo_config_file::make_lockfile_copy,
cargo_workspace::MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH, utf8_stdout,
}; };
/// Output of the build script and proc-macro building steps for a workspace. /// Output of the build script and proc-macro building steps for a workspace.
@ -30,6 +32,15 @@ pub struct WorkspaceBuildScripts {
error: Option<String>, error: Option<String>,
} }
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum ProcMacroDylibPath {
Path(AbsPathBuf),
DylibNotFound,
NotProcMacro,
#[default]
NotBuilt,
}
/// Output of the build script and proc-macro building step for a concrete package. /// Output of the build script and proc-macro building step for a concrete package.
#[derive(Debug, Clone, Default, PartialEq, Eq)] #[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct BuildScriptOutput { pub(crate) struct BuildScriptOutput {
@ -43,7 +54,7 @@ pub(crate) struct BuildScriptOutput {
/// Directory where a build script might place its output. /// Directory where a build script might place its output.
pub(crate) out_dir: Option<AbsPathBuf>, pub(crate) out_dir: Option<AbsPathBuf>,
/// Path to the proc-macro library file if this package exposes proc-macros. /// Path to the proc-macro library file if this package exposes proc-macros.
pub(crate) proc_macro_dylib_path: Option<AbsPathBuf>, pub(crate) proc_macro_dylib_path: ProcMacroDylibPath,
} }
impl BuildScriptOutput { impl BuildScriptOutput {
@ -51,7 +62,10 @@ impl BuildScriptOutput {
self.cfgs.is_empty() self.cfgs.is_empty()
&& self.envs.is_empty() && self.envs.is_empty()
&& self.out_dir.is_none() && self.out_dir.is_none()
&& self.proc_macro_dylib_path.is_none() && matches!(
self.proc_macro_dylib_path,
ProcMacroDylibPath::NotBuilt | ProcMacroDylibPath::NotProcMacro
)
} }
} }
@ -67,7 +81,7 @@ impl WorkspaceBuildScripts {
let current_dir = workspace.workspace_root(); let current_dir = workspace.workspace_root();
let allowed_features = workspace.workspace_features(); let allowed_features = workspace.workspace_features();
let cmd = Self::build_command( let (_guard, cmd) = Self::build_command(
config, config,
&allowed_features, &allowed_features,
workspace.manifest_path(), workspace.manifest_path(),
@ -88,7 +102,7 @@ impl WorkspaceBuildScripts {
) -> io::Result<Vec<WorkspaceBuildScripts>> { ) -> io::Result<Vec<WorkspaceBuildScripts>> {
assert_eq!(config.invocation_strategy, InvocationStrategy::Once); assert_eq!(config.invocation_strategy, InvocationStrategy::Once);
let cmd = Self::build_command( let (_guard, cmd) = Self::build_command(
config, config,
&Default::default(), &Default::default(),
// This is not gonna be used anyways, so just construct a dummy here // This is not gonna be used anyways, so just construct a dummy here
@ -126,6 +140,8 @@ impl WorkspaceBuildScripts {
|package, cb| { |package, cb| {
if let Some(&(package, workspace)) = by_id.get(package) { if let Some(&(package, workspace)) = by_id.get(package) {
cb(&workspaces[workspace][package].name, &mut res[workspace].outputs[package]); cb(&workspaces[workspace][package].name, &mut res[workspace].outputs[package]);
} else {
never!("Received compiler message for unknown package: {}", package);
} }
}, },
progress, progress,
@ -140,12 +156,9 @@ impl WorkspaceBuildScripts {
if tracing::enabled!(tracing::Level::INFO) { if tracing::enabled!(tracing::Level::INFO) {
for (idx, workspace) in workspaces.iter().enumerate() { for (idx, workspace) in workspaces.iter().enumerate() {
for package in workspace.packages() { for package in workspace.packages() {
let package_build_data = &mut res[idx].outputs[package]; let package_build_data: &mut BuildScriptOutput = &mut res[idx].outputs[package];
if !package_build_data.is_empty() { if !package_build_data.is_empty() {
tracing::info!( tracing::info!("{}: {package_build_data:?}", workspace[package].manifest,);
"{}: {package_build_data:?}",
workspace[package].manifest.parent(),
);
} }
} }
} }
@ -198,10 +211,33 @@ impl WorkspaceBuildScripts {
let path = dir_entry.path(); let path = dir_entry.path();
let extension = path.extension()?; let extension = path.extension()?;
if extension == std::env::consts::DLL_EXTENSION { if extension == std::env::consts::DLL_EXTENSION {
let name = path.file_stem()?.to_str()?.split_once('-')?.0.to_owned(); let name = path
let path = AbsPathBuf::try_from(Utf8PathBuf::from_path_buf(path).ok()?) .file_stem()?
.ok()?; .to_str()?
return Some((name, path)); .split_once('-')?
.0
.trim_start_matches("lib")
.to_owned();
let path = match Utf8PathBuf::from_path_buf(path) {
Ok(path) => path,
Err(path) => {
tracing::warn!(
"Proc-macro dylib path contains non-UTF8 characters: {:?}",
path.display()
);
return None;
}
};
return match AbsPathBuf::try_from(path) {
Ok(path) => Some((name, path)),
Err(path) => {
tracing::error!(
"proc-macro dylib path is not absolute: {:?}",
path
);
None
}
};
} }
} }
None None
@ -209,28 +245,24 @@ impl WorkspaceBuildScripts {
.collect(); .collect();
for p in rustc.packages() { for p in rustc.packages() {
let package = &rustc[p]; let package = &rustc[p];
if package bs.outputs[p].proc_macro_dylib_path =
.targets if package.targets.iter().any(|&it| {
.iter() matches!(rustc[it].kind, TargetKind::Lib { is_proc_macro: true })
.any(|&it| matches!(rustc[it].kind, TargetKind::Lib { is_proc_macro: true })) }) {
{ match proc_macro_dylibs.iter().find(|(name, _)| *name == package.name) {
if let Some((_, path)) = proc_macro_dylibs Some((_, path)) => ProcMacroDylibPath::Path(path.clone()),
.iter() _ => ProcMacroDylibPath::DylibNotFound,
.find(|(name, _)| *name.trim_start_matches("lib") == package.name) }
{ } else {
bs.outputs[p].proc_macro_dylib_path = Some(path.clone()); ProcMacroDylibPath::NotProcMacro
} }
}
} }
if tracing::enabled!(tracing::Level::INFO) { if tracing::enabled!(tracing::Level::INFO) {
for package in rustc.packages() { for package in rustc.packages() {
let package_build_data = &bs.outputs[package]; let package_build_data = &bs.outputs[package];
if !package_build_data.is_empty() { if !package_build_data.is_empty() {
tracing::info!( tracing::info!("{}: {package_build_data:?}", rustc[package].manifest,);
"{}: {package_build_data:?}",
rustc[package].manifest.parent(),
);
} }
} }
} }
@ -263,6 +295,12 @@ impl WorkspaceBuildScripts {
|package, cb| { |package, cb| {
if let Some(&package) = by_id.get(package) { if let Some(&package) = by_id.get(package) {
cb(&workspace[package].name, &mut outputs[package]); cb(&workspace[package].name, &mut outputs[package]);
} else {
never!(
"Received compiler message for unknown package: {}\n {}",
package,
by_id.keys().join(", ")
);
} }
}, },
progress, progress,
@ -272,10 +310,7 @@ impl WorkspaceBuildScripts {
for package in workspace.packages() { for package in workspace.packages() {
let package_build_data = &outputs[package]; let package_build_data = &outputs[package];
if !package_build_data.is_empty() { if !package_build_data.is_empty() {
tracing::info!( tracing::info!("{}: {package_build_data:?}", workspace[package].manifest,);
"{}: {package_build_data:?}",
workspace[package].manifest.parent(),
);
} }
} }
} }
@ -348,15 +383,23 @@ impl WorkspaceBuildScripts {
progress(format!( progress(format!(
"building compile-time-deps: proc-macro {name} built" "building compile-time-deps: proc-macro {name} built"
)); ));
if message.target.kind.contains(&cargo_metadata::TargetKind::ProcMacro) if data.proc_macro_dylib_path == ProcMacroDylibPath::NotBuilt {
data.proc_macro_dylib_path = ProcMacroDylibPath::NotProcMacro;
}
if !matches!(data.proc_macro_dylib_path, ProcMacroDylibPath::Path(_))
&& message
.target
.kind
.contains(&cargo_metadata::TargetKind::ProcMacro)
{ {
// Skip rmeta file data.proc_macro_dylib_path =
if let Some(filename) = match message.filenames.iter().find(|file| is_dylib(file)) {
message.filenames.iter().find(|file| is_dylib(file)) Some(filename) => {
{ let filename = AbsPath::assert(filename);
let filename = AbsPath::assert(filename); ProcMacroDylibPath::Path(filename.to_owned())
data.proc_macro_dylib_path = Some(filename.to_owned()); }
} None => ProcMacroDylibPath::DylibNotFound,
};
} }
}); });
} }
@ -393,14 +436,15 @@ impl WorkspaceBuildScripts {
current_dir: &AbsPath, current_dir: &AbsPath,
sysroot: &Sysroot, sysroot: &Sysroot,
toolchain: Option<&semver::Version>, toolchain: Option<&semver::Version>,
) -> io::Result<Command> { ) -> io::Result<(Option<temp_dir::TempDir>, Command)> {
match config.run_build_script_command.as_deref() { match config.run_build_script_command.as_deref() {
Some([program, args @ ..]) => { Some([program, args @ ..]) => {
let mut cmd = toolchain::command(program, current_dir, &config.extra_env); let mut cmd = toolchain::command(program, current_dir, &config.extra_env);
cmd.args(args); cmd.args(args);
Ok(cmd) Ok((None, cmd))
} }
_ => { _ => {
let mut requires_unstable_options = false;
let mut cmd = sysroot.tool(Tool::Cargo, current_dir, &config.extra_env); let mut cmd = sysroot.tool(Tool::Cargo, current_dir, &config.extra_env);
cmd.args(["check", "--quiet", "--workspace", "--message-format=json"]); cmd.args(["check", "--quiet", "--workspace", "--message-format=json"]);
@ -416,7 +460,19 @@ impl WorkspaceBuildScripts {
if let Some(target) = &config.target { if let Some(target) = &config.target {
cmd.args(["--target", target]); cmd.args(["--target", target]);
} }
let mut temp_dir_guard = None;
if toolchain
.is_some_and(|v| *v >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH)
{
let lockfile_path =
<_ as AsRef<Utf8Path>>::as_ref(manifest_path).with_extension("lock");
if let Some((temp_dir, target_lockfile)) = make_lockfile_copy(&lockfile_path) {
requires_unstable_options = true;
temp_dir_guard = Some(temp_dir);
cmd.arg("--lockfile-path");
cmd.arg(target_lockfile.as_str());
}
}
match &config.features { match &config.features {
CargoFeatures::All => { CargoFeatures::All => {
cmd.arg("--all-features"); cmd.arg("--all-features");
@ -438,6 +494,7 @@ impl WorkspaceBuildScripts {
} }
if manifest_path.is_rust_manifest() { if manifest_path.is_rust_manifest() {
requires_unstable_options = true;
cmd.arg("-Zscript"); cmd.arg("-Zscript");
} }
@ -457,8 +514,7 @@ impl WorkspaceBuildScripts {
toolchain.is_some_and(|v| *v >= COMP_TIME_DEPS_MIN_TOOLCHAIN_VERSION); toolchain.is_some_and(|v| *v >= COMP_TIME_DEPS_MIN_TOOLCHAIN_VERSION);
if cargo_comp_time_deps_available { if cargo_comp_time_deps_available {
cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly"); requires_unstable_options = true;
cmd.arg("-Zunstable-options");
cmd.arg("--compile-time-deps"); cmd.arg("--compile-time-deps");
// we can pass this unconditionally, because we won't actually build the // we can pass this unconditionally, because we won't actually build the
// binaries, and as such, this will succeed even on targets without libtest // binaries, and as such, this will succeed even on targets without libtest
@ -481,7 +537,11 @@ impl WorkspaceBuildScripts {
cmd.env("RA_RUSTC_WRAPPER", "1"); cmd.env("RA_RUSTC_WRAPPER", "1");
} }
} }
Ok(cmd) if requires_unstable_options {
cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
cmd.arg("-Zunstable-options");
}
Ok((temp_dir_guard, cmd))
} }
} }
} }

View file

@ -1,4 +1,5 @@
//! Read `.cargo/config.toml` as a JSON object //! Read `.cargo/config.toml` as a JSON object
use paths::{Utf8Path, Utf8PathBuf};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use toolchain::Tool; use toolchain::Tool;
@ -32,3 +33,24 @@ pub(crate) fn read(
Some(json) Some(json)
} }
pub(crate) fn make_lockfile_copy(
lockfile_path: &Utf8Path,
) -> Option<(temp_dir::TempDir, Utf8PathBuf)> {
let temp_dir = temp_dir::TempDir::with_prefix("rust-analyzer").ok()?;
let target_lockfile = temp_dir.path().join("Cargo.lock").try_into().ok()?;
match std::fs::copy(lockfile_path, &target_lockfile) {
Ok(_) => {
tracing::debug!("Copied lock file from `{}` to `{}`", lockfile_path, target_lockfile);
Some((temp_dir, target_lockfile))
}
// lockfile does not yet exist, so we can just create a new one in the temp dir
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Some((temp_dir, target_lockfile)),
Err(e) => {
tracing::warn!(
"Failed to copy lock file from `{lockfile_path}` to `{target_lockfile}`: {e}",
);
None
}
}
}

View file

@ -15,16 +15,18 @@ use span::Edition;
use stdx::process::spawn_with_streaming_output; use stdx::process::spawn_with_streaming_output;
use toolchain::Tool; use toolchain::Tool;
use crate::cargo_config_file::make_lockfile_copy;
use crate::{CfgOverrides, InvocationStrategy}; use crate::{CfgOverrides, InvocationStrategy};
use crate::{ManifestPath, Sysroot}; use crate::{ManifestPath, Sysroot};
const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH: semver::Version = semver::Version { pub(crate) const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH: semver::Version =
major: 1, semver::Version {
minor: 82, major: 1,
patch: 0, minor: 82,
pre: semver::Prerelease::EMPTY, patch: 0,
build: semver::BuildMetadata::EMPTY, pre: semver::Prerelease::EMPTY,
}; build: semver::BuildMetadata::EMPTY,
};
/// [`CargoWorkspace`] represents the logical structure of, well, a Cargo /// [`CargoWorkspace`] represents the logical structure of, well, a Cargo
/// workspace. It pretty closely mirrors `cargo metadata` output. /// workspace. It pretty closely mirrors `cargo metadata` output.
@ -245,7 +247,7 @@ pub enum TargetKind {
} }
impl TargetKind { impl TargetKind {
fn new(kinds: &[cargo_metadata::TargetKind]) -> TargetKind { pub fn new(kinds: &[cargo_metadata::TargetKind]) -> TargetKind {
for kind in kinds { for kind in kinds {
return match kind { return match kind {
cargo_metadata::TargetKind::Bin => TargetKind::Bin, cargo_metadata::TargetKind::Bin => TargetKind::Bin,
@ -552,7 +554,10 @@ impl CargoWorkspace {
pub(crate) struct FetchMetadata { pub(crate) struct FetchMetadata {
command: cargo_metadata::MetadataCommand, command: cargo_metadata::MetadataCommand,
#[expect(dead_code)]
manifest_path: ManifestPath,
lockfile_path: Option<Utf8PathBuf>, lockfile_path: Option<Utf8PathBuf>,
#[expect(dead_code)]
kind: &'static str, kind: &'static str,
no_deps: bool, no_deps: bool,
no_deps_result: anyhow::Result<cargo_metadata::Metadata>, no_deps_result: anyhow::Result<cargo_metadata::Metadata>,
@ -596,25 +601,22 @@ impl FetchMetadata {
} }
command.current_dir(current_dir); command.current_dir(current_dir);
let mut needs_nightly = false;
let mut other_options = vec![]; let mut other_options = vec![];
// cargo metadata only supports a subset of flags of what cargo usually accepts, and usually // cargo metadata only supports a subset of flags of what cargo usually accepts, and usually
// the only relevant flags for metadata here are unstable ones, so we pass those along // the only relevant flags for metadata here are unstable ones, so we pass those along
// but nothing else // but nothing else
let mut extra_args = config.extra_args.iter(); let mut extra_args = config.extra_args.iter();
while let Some(arg) = extra_args.next() { while let Some(arg) = extra_args.next() {
if arg == "-Z" { if arg == "-Z"
if let Some(arg) = extra_args.next() { && let Some(arg) = extra_args.next()
needs_nightly = true; {
other_options.push("-Z".to_owned()); other_options.push("-Z".to_owned());
other_options.push(arg.to_owned()); other_options.push(arg.to_owned());
}
} }
} }
let mut lockfile_path = None; let mut lockfile_path = None;
if cargo_toml.is_rust_manifest() { if cargo_toml.is_rust_manifest() {
needs_nightly = true;
other_options.push("-Zscript".to_owned()); other_options.push("-Zscript".to_owned());
} else if config } else if config
.toolchain_version .toolchain_version
@ -632,10 +634,6 @@ impl FetchMetadata {
command.other_options(other_options.clone()); command.other_options(other_options.clone());
if needs_nightly {
command.env("RUSTC_BOOTSTRAP", "1");
}
// Pre-fetch basic metadata using `--no-deps`, which: // Pre-fetch basic metadata using `--no-deps`, which:
// - avoids fetching registries like crates.io, // - avoids fetching registries like crates.io,
// - skips dependency resolution and does not modify lockfiles, // - skips dependency resolution and does not modify lockfiles,
@ -655,7 +653,15 @@ impl FetchMetadata {
} }
.with_context(|| format!("Failed to run `{cargo_command:?}`")); .with_context(|| format!("Failed to run `{cargo_command:?}`"));
Self { command, lockfile_path, kind: config.kind, no_deps, no_deps_result, other_options } Self {
manifest_path: cargo_toml.clone(),
command,
lockfile_path,
kind: config.kind,
no_deps,
no_deps_result,
other_options,
}
} }
pub(crate) fn no_deps_metadata(&self) -> Option<&cargo_metadata::Metadata> { pub(crate) fn no_deps_metadata(&self) -> Option<&cargo_metadata::Metadata> {
@ -672,40 +678,34 @@ impl FetchMetadata {
locked: bool, locked: bool,
progress: &dyn Fn(String), progress: &dyn Fn(String),
) -> anyhow::Result<(cargo_metadata::Metadata, Option<anyhow::Error>)> { ) -> anyhow::Result<(cargo_metadata::Metadata, Option<anyhow::Error>)> {
let Self { mut command, lockfile_path, kind, no_deps, no_deps_result, mut other_options } = _ = target_dir;
self; let Self {
mut command,
manifest_path: _,
lockfile_path,
kind: _,
no_deps,
no_deps_result,
mut other_options,
} = self;
if no_deps { if no_deps {
return no_deps_result.map(|m| (m, None)); return no_deps_result.map(|m| (m, None));
} }
let mut using_lockfile_copy = false; let mut using_lockfile_copy = false;
// The manifest is a rust file, so this means its a script manifest let mut _temp_dir_guard;
if let Some(lockfile) = lockfile_path { if let Some(lockfile) = lockfile_path
let target_lockfile = && let Some((temp_dir, target_lockfile)) = make_lockfile_copy(&lockfile)
target_dir.join("rust-analyzer").join("metadata").join(kind).join("Cargo.lock"); {
match std::fs::copy(&lockfile, &target_lockfile) { _temp_dir_guard = temp_dir;
Ok(_) => { other_options.push("--lockfile-path".to_owned());
using_lockfile_copy = true; other_options.push(target_lockfile.to_string());
other_options.push("--lockfile-path".to_owned()); using_lockfile_copy = true;
other_options.push(target_lockfile.to_string());
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
// There exists no lockfile yet
using_lockfile_copy = true;
other_options.push("--lockfile-path".to_owned());
other_options.push(target_lockfile.to_string());
}
Err(e) => {
tracing::warn!(
"Failed to copy lock file from `{lockfile}` to `{target_lockfile}`: {e}",
);
}
}
} }
if using_lockfile_copy { if using_lockfile_copy || other_options.iter().any(|it| it.starts_with("-Z")) {
command.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
other_options.push("-Zunstable-options".to_owned()); other_options.push("-Zunstable-options".to_owned());
command.env("RUSTC_BOOTSTRAP", "1");
} }
// No need to lock it if we copied the lockfile, we won't modify the original after all/ // No need to lock it if we copied the lockfile, we won't modify the original after all/
// This way cargo cannot error out on us if the lockfile requires updating. // This way cargo cannot error out on us if the lockfile requires updating.
@ -714,13 +714,11 @@ impl FetchMetadata {
} }
command.other_options(other_options); command.other_options(other_options);
// FIXME: Fetching metadata is a slow process, as it might require
// calling crates.io. We should be reporting progress here, but it's
// unclear whether cargo itself supports it.
progress("cargo metadata: started".to_owned()); progress("cargo metadata: started".to_owned());
let res = (|| -> anyhow::Result<(_, _)> { let res = (|| -> anyhow::Result<(_, _)> {
let mut errored = false; let mut errored = false;
tracing::debug!("Running `{:?}`", command.cargo_command());
let output = let output =
spawn_with_streaming_output(command.cargo_command(), &mut |_| (), &mut |line| { spawn_with_streaming_output(command.cargo_command(), &mut |_| (), &mut |line| {
errored = errored || line.starts_with("error") || line.starts_with("warning"); errored = errored || line.starts_with("error") || line.starts_with("warning");

View file

@ -59,7 +59,7 @@ use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
pub use crate::{ pub use crate::{
build_dependencies::WorkspaceBuildScripts, build_dependencies::{ProcMacroDylibPath, WorkspaceBuildScripts},
cargo_workspace::{ cargo_workspace::{
CargoConfig, CargoFeatures, CargoMetadataConfig, CargoWorkspace, Package, PackageData, CargoConfig, CargoFeatures, CargoMetadataConfig, CargoWorkspace, Package, PackageData,
PackageDependency, RustLibSource, Target, TargetData, TargetKind, PackageDependency, RustLibSource, Target, TargetData, TargetKind,
@ -139,21 +139,22 @@ impl ProjectManifest {
} }
fn find_in_parent_dirs(path: &AbsPath, target_file_name: &str) -> Option<ManifestPath> { fn find_in_parent_dirs(path: &AbsPath, target_file_name: &str) -> Option<ManifestPath> {
if path.file_name().unwrap_or_default() == target_file_name { if path.file_name().unwrap_or_default() == target_file_name
if let Ok(manifest) = ManifestPath::try_from(path.to_path_buf()) { && let Ok(manifest) = ManifestPath::try_from(path.to_path_buf())
return Some(manifest); {
} return Some(manifest);
} }
let mut curr = Some(path); let mut curr = Some(path);
while let Some(path) = curr { while let Some(path) = curr {
let candidate = path.join(target_file_name); let candidate = path.join(target_file_name);
if fs::metadata(&candidate).is_ok() { if fs::metadata(&candidate).is_ok()
if let Ok(manifest) = ManifestPath::try_from(candidate) { && let Ok(manifest) = ManifestPath::try_from(candidate)
return Some(manifest); {
} return Some(manifest);
} }
curr = path.parent(); curr = path.parent();
} }

View file

@ -143,12 +143,11 @@ impl Sysroot {
Some(root) => { Some(root) => {
// special case rustc, we can look that up directly in the sysroot's bin folder // special case rustc, we can look that up directly in the sysroot's bin folder
// as it should never invoke another cargo binary // as it should never invoke another cargo binary
if let Tool::Rustc = tool { if let Tool::Rustc = tool
if let Some(path) = && let Some(path) =
probe_for_binary(root.join("bin").join(Tool::Rustc.name()).into()) probe_for_binary(root.join("bin").join(Tool::Rustc.name()).into())
{ {
return toolchain::command(path, current_dir, envs); return toolchain::command(path, current_dir, envs);
}
} }
let mut cmd = toolchain::command(tool.prefer_proxy(), current_dir, envs); let mut cmd = toolchain::command(tool.prefer_proxy(), current_dir, envs);
@ -291,29 +290,26 @@ impl Sysroot {
pub fn set_workspace(&mut self, workspace: RustLibSrcWorkspace) { pub fn set_workspace(&mut self, workspace: RustLibSrcWorkspace) {
self.workspace = workspace; self.workspace = workspace;
if self.error.is_none() { if self.error.is_none()
if let Some(src_root) = &self.rust_lib_src_root { && let Some(src_root) = &self.rust_lib_src_root
let has_core = match &self.workspace { {
RustLibSrcWorkspace::Workspace(ws) => { let has_core = match &self.workspace {
ws.packages().any(|p| ws[p].name == "core") RustLibSrcWorkspace::Workspace(ws) => ws.packages().any(|p| ws[p].name == "core"),
} RustLibSrcWorkspace::Json(project_json) => project_json
RustLibSrcWorkspace::Json(project_json) => project_json .crates()
.crates() .filter_map(|(_, krate)| krate.display_name.clone())
.filter_map(|(_, krate)| krate.display_name.clone()) .any(|name| name.canonical_name().as_str() == "core"),
.any(|name| name.canonical_name().as_str() == "core"), RustLibSrcWorkspace::Stitched(stitched) => stitched.by_name("core").is_some(),
RustLibSrcWorkspace::Stitched(stitched) => stitched.by_name("core").is_some(), RustLibSrcWorkspace::Empty => true,
RustLibSrcWorkspace::Empty => true, };
if !has_core {
let var_note = if env::var_os("RUST_SRC_PATH").is_some() {
" (env var `RUST_SRC_PATH` is set and may be incorrect, try unsetting it)"
} else {
", try running `rustup component add rust-src` to possibly fix this"
}; };
if !has_core { self.error =
let var_note = if env::var_os("RUST_SRC_PATH").is_some() { Some(format!("sysroot at `{src_root}` is missing a `core` library{var_note}",));
" (env var `RUST_SRC_PATH` is set and may be incorrect, try unsetting it)"
} else {
", try running `rustup component add rust-src` to possibly fix this"
};
self.error = Some(format!(
"sysroot at `{src_root}` is missing a `core` library{var_note}",
));
}
} }
} }
} }

View file

@ -65,6 +65,7 @@ fn rustc_print_cfg(
let (sysroot, current_dir) = match config { let (sysroot, current_dir) = match config {
QueryConfig::Cargo(sysroot, cargo_toml, _) => { QueryConfig::Cargo(sysroot, cargo_toml, _) => {
let mut cmd = sysroot.tool(Tool::Cargo, cargo_toml.parent(), extra_env); let mut cmd = sysroot.tool(Tool::Cargo, cargo_toml.parent(), extra_env);
cmd.env("__CARGO_TEST_CHANNEL_OVERRIDE_DO_NOT_USE_THIS", "nightly");
cmd.args(["rustc", "-Z", "unstable-options"]).args(RUSTC_ARGS); cmd.args(["rustc", "-Z", "unstable-options"]).args(RUSTC_ARGS);
if let Some(target) = target { if let Some(target) = target {
cmd.args(["--target", target]); cmd.args(["--target", target]);

View file

@ -24,7 +24,7 @@ use crate::{
CargoConfig, CargoWorkspace, CfgOverrides, InvocationStrategy, ManifestPath, Package, CargoConfig, CargoWorkspace, CfgOverrides, InvocationStrategy, ManifestPath, Package,
ProjectJson, ProjectManifest, RustSourceWorkspaceConfig, Sysroot, TargetData, TargetKind, ProjectJson, ProjectManifest, RustSourceWorkspaceConfig, Sysroot, TargetData, TargetKind,
WorkspaceBuildScripts, WorkspaceBuildScripts,
build_dependencies::BuildScriptOutput, build_dependencies::{BuildScriptOutput, ProcMacroDylibPath},
cargo_config_file, cargo_config_file,
cargo_workspace::{CargoMetadataConfig, DepKind, FetchMetadata, PackageData, RustLibSource}, cargo_workspace::{CargoMetadataConfig, DepKind, FetchMetadata, PackageData, RustLibSource},
env::{cargo_config_env, inject_cargo_env, inject_cargo_package_env, inject_rustc_tool_env}, env::{cargo_config_env, inject_cargo_env, inject_cargo_package_env, inject_rustc_tool_env},
@ -424,12 +424,12 @@ impl ProjectWorkspace {
sysroot.set_workspace(loaded_sysroot); sysroot.set_workspace(loaded_sysroot);
} }
if !cargo.requires_rustc_private() { if !cargo.requires_rustc_private()
if let Err(e) = &mut rustc { && let Err(e) = &mut rustc
// We don't need the rustc sources here, {
// so just discard the error. // We don't need the rustc sources here,
_ = e.take(); // so just discard the error.
} _ = e.take();
} }
Ok(ProjectWorkspace { Ok(ProjectWorkspace {
@ -1163,17 +1163,15 @@ fn project_json_to_crate_graph(
crate = display_name.as_ref().map(|name| name.canonical_name().as_str()), crate = display_name.as_ref().map(|name| name.canonical_name().as_str()),
"added root to crate graph" "added root to crate graph"
); );
if *is_proc_macro { if *is_proc_macro && let Some(path) = proc_macro_dylib_path.clone() {
if let Some(path) = proc_macro_dylib_path.clone() { let node = Ok((
let node = Ok(( display_name
display_name .as_ref()
.as_ref() .map(|it| it.canonical_name().as_str().to_owned())
.map(|it| it.canonical_name().as_str().to_owned()) .unwrap_or_else(|| format!("crate{}", idx.0)),
.unwrap_or_else(|| format!("crate{}", idx.0)), path,
path, ));
)); proc_macros.insert(crate_graph_crate_id, node);
proc_macros.insert(crate_graph_crate_id, node);
}
} }
(idx, crate_graph_crate_id) (idx, crate_graph_crate_id)
}, },
@ -1318,16 +1316,17 @@ fn cargo_to_crate_graph(
public_deps.add_to_crate_graph(crate_graph, from); public_deps.add_to_crate_graph(crate_graph, from);
// Add dep edge of all targets to the package's lib target // Add dep edge of all targets to the package's lib target
if let Some((to, name)) = lib_tgt.clone() { if let Some((to, name)) = lib_tgt.clone()
if to != from && kind != TargetKind::BuildScript { && to != from
// (build script can not depend on its library target) && kind != TargetKind::BuildScript
{
// (build script can not depend on its library target)
// For root projects with dashes in their name, // For root projects with dashes in their name,
// cargo metadata does not do any normalization, // cargo metadata does not do any normalization,
// so we do it ourselves currently // so we do it ourselves currently
let name = CrateName::normalize_dashes(&name); let name = CrateName::normalize_dashes(&name);
add_dep(crate_graph, from, name, to); add_dep(crate_graph, from, name, to);
}
} }
} }
} }
@ -1638,9 +1637,19 @@ fn add_target_crate_root(
let proc_macro = match build_data { let proc_macro = match build_data {
Some((BuildScriptOutput { proc_macro_dylib_path, .. }, has_errors)) => { Some((BuildScriptOutput { proc_macro_dylib_path, .. }, has_errors)) => {
match proc_macro_dylib_path { match proc_macro_dylib_path {
Some(path) => Ok((cargo_name.to_owned(), path.clone())), ProcMacroDylibPath::Path(path) => Ok((cargo_name.to_owned(), path.clone())),
None if has_errors => Err(ProcMacroLoadingError::FailedToBuild), ProcMacroDylibPath::NotBuilt => Err(ProcMacroLoadingError::NotYetBuilt),
None => Err(ProcMacroLoadingError::MissingDylibPath), ProcMacroDylibPath::NotProcMacro | ProcMacroDylibPath::DylibNotFound
if has_errors =>
{
Err(ProcMacroLoadingError::FailedToBuild)
}
ProcMacroDylibPath::NotProcMacro => {
Err(ProcMacroLoadingError::ExpectedProcMacroArtifact)
}
ProcMacroDylibPath::DylibNotFound => {
Err(ProcMacroLoadingError::MissingDylibPath)
}
} }
} }
None => Err(ProcMacroLoadingError::NotYetBuilt), None => Err(ProcMacroLoadingError::NotYetBuilt),
@ -1905,7 +1914,8 @@ fn cargo_target_dir(
meta.manifest_path(manifest); meta.manifest_path(manifest);
// `--no-deps` doesn't (over)write lockfiles as it doesn't do any package resolve. // `--no-deps` doesn't (over)write lockfiles as it doesn't do any package resolve.
// So we can use it to get `target_directory` before copying lockfiles // So we can use it to get `target_directory` before copying lockfiles
let mut other_options = vec!["--no-deps".to_owned()]; meta.no_deps();
let mut other_options = vec![];
if manifest.is_rust_manifest() { if manifest.is_rust_manifest() {
meta.env("RUSTC_BOOTSTRAP", "1"); meta.env("RUSTC_BOOTSTRAP", "1");
other_options.push("-Zscript".to_owned()); other_options.push("-Zscript".to_owned());

View file

@ -656,22 +656,26 @@ impl flags::AnalysisStats {
let mut sw = self.stop_watch(); let mut sw = self.stop_watch();
let mut all = 0; let mut all = 0;
let mut fail = 0; let mut fail = 0;
for &body in bodies { for &body_id in bodies {
if matches!(body, DefWithBody::Variant(_)) { if matches!(body_id, DefWithBody::Variant(_)) {
continue; continue;
} }
let module = body_id.module(db);
if !self.should_process(db, body_id, module) {
continue;
}
all += 1; all += 1;
let Err(e) = db.mir_body(body.into()) else { let Err(e) = db.mir_body(body_id.into()) else {
continue; continue;
}; };
if verbosity.is_spammy() { if verbosity.is_spammy() {
let full_name = body let full_name = module
.module(db)
.path_to_root(db) .path_to_root(db)
.into_iter() .into_iter()
.rev() .rev()
.filter_map(|it| it.name(db)) .filter_map(|it| it.name(db))
.chain(Some(body.name(db).unwrap_or_else(Name::missing))) .chain(Some(body_id.name(db).unwrap_or_else(Name::missing)))
.map(|it| it.display(db, Edition::LATEST).to_string()) .map(|it| it.display(db, Edition::LATEST).to_string())
.join("::"); .join("::");
bar.println(format!("Mir body for {full_name} failed due {e:?}")); bar.println(format!("Mir body for {full_name} failed due {e:?}"));
@ -727,26 +731,9 @@ impl flags::AnalysisStats {
let name = body_id.name(db).unwrap_or_else(Name::missing); let name = body_id.name(db).unwrap_or_else(Name::missing);
let module = body_id.module(db); let module = body_id.module(db);
let display_target = module.krate().to_display_target(db); let display_target = module.krate().to_display_target(db);
let full_name = move || {
module
.krate()
.display_name(db)
.map(|it| it.canonical_name().as_str().to_owned())
.into_iter()
.chain(
module
.path_to_root(db)
.into_iter()
.filter_map(|it| it.name(db))
.rev()
.chain(Some(body_id.name(db).unwrap_or_else(Name::missing)))
.map(|it| it.display(db, Edition::LATEST).to_string()),
)
.join("::")
};
if let Some(only_name) = self.only.as_deref() { if let Some(only_name) = self.only.as_deref() {
if name.display(db, Edition::LATEST).to_string() != only_name if name.display(db, Edition::LATEST).to_string() != only_name
&& full_name() != only_name && full_name(db, body_id, module) != only_name
{ {
continue; continue;
} }
@ -763,12 +750,17 @@ impl flags::AnalysisStats {
let original_file = src.file_id.original_file(db); let original_file = src.file_id.original_file(db);
let path = vfs.file_path(original_file.file_id(db)); let path = vfs.file_path(original_file.file_id(db));
let syntax_range = src.text_range(); let syntax_range = src.text_range();
format!("processing: {} ({} {:?})", full_name(), path, syntax_range) format!(
"processing: {} ({} {:?})",
full_name(db, body_id, module),
path,
syntax_range
)
} else { } else {
format!("processing: {}", full_name()) format!("processing: {}", full_name(db, body_id, module))
} }
} else { } else {
format!("processing: {}", full_name()) format!("processing: {}", full_name(db, body_id, module))
} }
}; };
if verbosity.is_spammy() { if verbosity.is_spammy() {
@ -781,9 +773,11 @@ impl flags::AnalysisStats {
Ok(inference_result) => inference_result, Ok(inference_result) => inference_result,
Err(p) => { Err(p) => {
if let Some(s) = p.downcast_ref::<&str>() { if let Some(s) = p.downcast_ref::<&str>() {
eprintln!("infer panicked for {}: {}", full_name(), s); eprintln!("infer panicked for {}: {}", full_name(db, body_id, module), s);
} else if let Some(s) = p.downcast_ref::<String>() { } else if let Some(s) = p.downcast_ref::<String>() {
eprintln!("infer panicked for {}: {}", full_name(), s); eprintln!("infer panicked for {}: {}", full_name(db, body_id, module), s);
} else {
eprintln!("infer panicked for {}", full_name(db, body_id, module));
} }
panics += 1; panics += 1;
bar.inc(1); bar.inc(1);
@ -890,7 +884,7 @@ impl flags::AnalysisStats {
if verbosity.is_spammy() { if verbosity.is_spammy() {
bar.println(format!( bar.println(format!(
"In {}: {} exprs, {} unknown, {} partial", "In {}: {} exprs, {} unknown, {} partial",
full_name(), full_name(db, body_id, module),
num_exprs - previous_exprs, num_exprs - previous_exprs,
num_exprs_unknown - previous_unknown, num_exprs_unknown - previous_unknown,
num_exprs_partially_unknown - previous_partially_unknown num_exprs_partially_unknown - previous_partially_unknown
@ -993,7 +987,7 @@ impl flags::AnalysisStats {
if verbosity.is_spammy() { if verbosity.is_spammy() {
bar.println(format!( bar.println(format!(
"In {}: {} pats, {} unknown, {} partial", "In {}: {} pats, {} unknown, {} partial",
full_name(), full_name(db, body_id, module),
num_pats - previous_pats, num_pats - previous_pats,
num_pats_unknown - previous_unknown, num_pats_unknown - previous_unknown,
num_pats_partially_unknown - previous_partially_unknown num_pats_partially_unknown - previous_partially_unknown
@ -1049,34 +1043,8 @@ impl flags::AnalysisStats {
bar.tick(); bar.tick();
for &body_id in bodies { for &body_id in bodies {
let module = body_id.module(db); let module = body_id.module(db);
let full_name = move || { if !self.should_process(db, body_id, module) {
module continue;
.krate()
.display_name(db)
.map(|it| it.canonical_name().as_str().to_owned())
.into_iter()
.chain(
module
.path_to_root(db)
.into_iter()
.filter_map(|it| it.name(db))
.rev()
.chain(Some(body_id.name(db).unwrap_or_else(Name::missing)))
.map(|it| it.display(db, Edition::LATEST).to_string()),
)
.join("::")
};
if let Some(only_name) = self.only.as_deref() {
if body_id
.name(db)
.unwrap_or_else(Name::missing)
.display(db, Edition::LATEST)
.to_string()
!= only_name
&& full_name() != only_name
{
continue;
}
} }
let msg = move || { let msg = move || {
if verbosity.is_verbose() { if verbosity.is_verbose() {
@ -1090,12 +1058,17 @@ impl flags::AnalysisStats {
let original_file = src.file_id.original_file(db); let original_file = src.file_id.original_file(db);
let path = vfs.file_path(original_file.file_id(db)); let path = vfs.file_path(original_file.file_id(db));
let syntax_range = src.text_range(); let syntax_range = src.text_range();
format!("processing: {} ({} {:?})", full_name(), path, syntax_range) format!(
"processing: {} ({} {:?})",
full_name(db, body_id, module),
path,
syntax_range
)
} else { } else {
format!("processing: {}", full_name()) format!("processing: {}", full_name(db, body_id, module))
} }
} else { } else {
format!("processing: {}", full_name()) format!("processing: {}", full_name(db, body_id, module))
} }
}; };
if verbosity.is_spammy() { if verbosity.is_spammy() {
@ -1205,11 +1178,42 @@ impl flags::AnalysisStats {
eprintln!("{:<20} {} ({} files)", "IDE:", ide_time, file_ids.len()); eprintln!("{:<20} {} ({} files)", "IDE:", ide_time, file_ids.len());
} }
fn should_process(&self, db: &RootDatabase, body_id: DefWithBody, module: hir::Module) -> bool {
if let Some(only_name) = self.only.as_deref() {
let name = body_id.name(db).unwrap_or_else(Name::missing);
if name.display(db, Edition::LATEST).to_string() != only_name
&& full_name(db, body_id, module) != only_name
{
return false;
}
}
true
}
fn stop_watch(&self) -> StopWatch { fn stop_watch(&self) -> StopWatch {
StopWatch::start() StopWatch::start()
} }
} }
fn full_name(db: &RootDatabase, body_id: DefWithBody, module: hir::Module) -> String {
module
.krate()
.display_name(db)
.map(|it| it.canonical_name().as_str().to_owned())
.into_iter()
.chain(
module
.path_to_root(db)
.into_iter()
.filter_map(|it| it.name(db))
.rev()
.chain(Some(body_id.name(db).unwrap_or_else(Name::missing)))
.map(|it| it.display(db, Edition::LATEST).to_string()),
)
.join("::")
}
fn location_csv_expr(db: &RootDatabase, vfs: &Vfs, sm: &BodySourceMap, expr_id: ExprId) -> String { fn location_csv_expr(db: &RootDatabase, vfs: &Vfs, sm: &BodySourceMap, expr_id: ExprId) -> String {
let src = match sm.expr_syntax(expr_id) { let src = match sm.expr_syntax(expr_id) {
Ok(s) => s, Ok(s) => s,

View file

@ -2162,6 +2162,7 @@ impl Config {
extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(), extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(),
extra_env: self.extra_env(source_root).clone(), extra_env: self.extra_env(source_root).clone(),
target_dir: self.target_dir_from_config(source_root), target_dir: self.target_dir_from_config(source_root),
set_test: true,
} }
} }
@ -2219,6 +2220,7 @@ impl Config {
extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(), extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(),
extra_env: self.check_extra_env(source_root), extra_env: self.check_extra_env(source_root),
target_dir: self.target_dir_from_config(source_root), target_dir: self.target_dir_from_config(source_root),
set_test: *self.cfg_setTest(source_root),
}, },
ansi_color_output: self.color_diagnostic_output(), ansi_color_output: self.color_diagnostic_output(),
}, },

View file

@ -31,6 +31,7 @@ pub(crate) enum InvocationStrategy {
pub(crate) struct CargoOptions { pub(crate) struct CargoOptions {
pub(crate) target_tuples: Vec<String>, pub(crate) target_tuples: Vec<String>,
pub(crate) all_targets: bool, pub(crate) all_targets: bool,
pub(crate) set_test: bool,
pub(crate) no_default_features: bool, pub(crate) no_default_features: bool,
pub(crate) all_features: bool, pub(crate) all_features: bool,
pub(crate) features: Vec<String>, pub(crate) features: Vec<String>,
@ -54,7 +55,13 @@ impl CargoOptions {
cmd.args(["--target", target.as_str()]); cmd.args(["--target", target.as_str()]);
} }
if self.all_targets { if self.all_targets {
cmd.arg("--all-targets"); if self.set_test {
cmd.arg("--all-targets");
} else {
// No --benches unfortunately, as this implies --tests (see https://github.com/rust-lang/cargo/issues/6454),
// and users setting `cfg.seTest = false` probably prefer disabling benches than enabling tests.
cmd.args(["--lib", "--bins", "--examples"]);
}
} }
if self.all_features { if self.all_features {
cmd.arg("--all-features"); cmd.arg("--all-features");
@ -104,7 +111,18 @@ impl fmt::Display for FlycheckConfig {
match self { match self {
FlycheckConfig::CargoCommand { command, .. } => write!(f, "cargo {command}"), FlycheckConfig::CargoCommand { command, .. } => write!(f, "cargo {command}"),
FlycheckConfig::CustomCommand { command, args, .. } => { FlycheckConfig::CustomCommand { command, args, .. } => {
write!(f, "{command} {}", args.join(" ")) // Don't show `my_custom_check --foo $saved_file` literally to the user, as it
// looks like we've forgotten to substitute $saved_file.
//
// Instead, show `my_custom_check --foo ...`. The
// actual path is often too long to be worth showing
// in the IDE (e.g. in the VS Code status bar).
let display_args = args
.iter()
.map(|arg| if arg == SAVED_FILE_PLACEHOLDER { "..." } else { arg })
.collect::<Vec<_>>();
write!(f, "{command} {}", display_args.join(" "))
} }
} }
} }

View file

@ -101,7 +101,7 @@ WhereClause =
'where' predicates:(WherePred (',' WherePred)* ','?) 'where' predicates:(WherePred (',' WherePred)* ','?)
WherePred = WherePred =
('for' GenericParamList)? (Lifetime | Type) ':' TypeBoundList? ForBinder? (Lifetime | Type) ':' TypeBoundList?
//*************************// //*************************//
@ -534,10 +534,10 @@ FieldExpr =
Attr* Expr '.' NameRef Attr* Expr '.' NameRef
ClosureExpr = ClosureExpr =
Attr* ClosureBinder? 'const'? 'static'? 'async'? 'gen'? 'move'? ParamList RetType? Attr* ForBinder? 'const'? 'static'? 'async'? 'gen'? 'move'? ParamList RetType?
body:Expr body:Expr
ClosureBinder = ForBinder =
'for' GenericParamList 'for' GenericParamList
IfExpr = IfExpr =
@ -658,7 +658,7 @@ FnPtrType =
'const'? 'async'? 'unsafe'? Abi? 'fn' ParamList RetType? 'const'? 'async'? 'unsafe'? Abi? 'fn' ParamList RetType?
ForType = ForType =
'for' GenericParamList Type ForBinder Type
ImplTraitType = ImplTraitType =
'impl' TypeBoundList 'impl' TypeBoundList
@ -671,7 +671,7 @@ TypeBoundList =
TypeBound = TypeBound =
Lifetime Lifetime
| ('~' 'const' | '[' 'const' ']' | 'const')? 'async'? '?'? Type | ForBinder? ('~' 'const' | '[' 'const' ']' | 'const')? 'async'? '?'? Type
| 'use' UseBoundGenericArgs | 'use' UseBoundGenericArgs
UseBoundGenericArgs = UseBoundGenericArgs =

View file

@ -393,8 +393,7 @@ where
let pred = predicates.next().unwrap(); let pred = predicates.next().unwrap();
let mut bounds = pred.type_bound_list().unwrap().bounds(); let mut bounds = pred.type_bound_list().unwrap().bounds();
assert!(pred.for_token().is_none()); assert!(pred.for_binder().is_none());
assert!(pred.generic_param_list().is_none());
assert_eq!("T", pred.ty().unwrap().syntax().text().to_string()); assert_eq!("T", pred.ty().unwrap().syntax().text().to_string());
assert_bound("Clone", bounds.next()); assert_bound("Clone", bounds.next());
assert_bound("Copy", bounds.next()); assert_bound("Copy", bounds.next());
@ -432,8 +431,10 @@ where
let pred = predicates.next().unwrap(); let pred = predicates.next().unwrap();
let mut bounds = pred.type_bound_list().unwrap().bounds(); let mut bounds = pred.type_bound_list().unwrap().bounds();
assert!(pred.for_token().is_some()); assert_eq!(
assert_eq!("<'a>", pred.generic_param_list().unwrap().syntax().text().to_string()); "<'a>",
pred.for_binder().unwrap().generic_param_list().unwrap().syntax().text().to_string()
);
assert_eq!("F", pred.ty().unwrap().syntax().text().to_string()); assert_eq!("F", pred.ty().unwrap().syntax().text().to_string());
assert_bound("Fn(&'a str)", bounds.next()); assert_bound("Fn(&'a str)", bounds.next());
} }

View file

@ -6,9 +6,12 @@ use std::{fmt, iter, ops};
use crate::{ use crate::{
AstToken, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken, AstToken, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken,
ast::{self, AstNode, make}, ast::{self, AstNode, make},
syntax_editor::{SyntaxEditor, SyntaxMappingBuilder},
ted, ted,
}; };
use super::syntax_factory::SyntaxFactory;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IndentLevel(pub u8); pub struct IndentLevel(pub u8);
@ -95,6 +98,24 @@ impl IndentLevel {
} }
} }
pub(super) fn clone_increase_indent(self, node: &SyntaxNode) -> SyntaxNode {
let node = node.clone_subtree();
let mut editor = SyntaxEditor::new(node.clone());
let tokens = node
.preorder_with_tokens()
.filter_map(|event| match event {
rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
_ => None,
})
.filter_map(ast::Whitespace::cast)
.filter(|ws| ws.text().contains('\n'));
for ws in tokens {
let new_ws = make::tokens::whitespace(&format!("{}{self}", ws.syntax()));
editor.replace(ws.syntax(), &new_ws);
}
editor.finish().new_root().clone()
}
pub(super) fn decrease_indent(self, node: &SyntaxNode) { pub(super) fn decrease_indent(self, node: &SyntaxNode) {
let tokens = node.preorder_with_tokens().filter_map(|event| match event { let tokens = node.preorder_with_tokens().filter_map(|event| match event {
rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it), rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
@ -111,36 +132,54 @@ impl IndentLevel {
} }
} }
} }
pub(super) fn clone_decrease_indent(self, node: &SyntaxNode) -> SyntaxNode {
let node = node.clone_subtree();
let mut editor = SyntaxEditor::new(node.clone());
let tokens = node
.preorder_with_tokens()
.filter_map(|event| match event {
rowan::WalkEvent::Leave(NodeOrToken::Token(it)) => Some(it),
_ => None,
})
.filter_map(ast::Whitespace::cast)
.filter(|ws| ws.text().contains('\n'));
for ws in tokens {
let new_ws =
make::tokens::whitespace(&ws.syntax().text().replace(&format!("\n{self}"), "\n"));
editor.replace(ws.syntax(), &new_ws);
}
editor.finish().new_root().clone()
}
} }
fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> { fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> {
iter::successors(Some(token), |token| token.prev_token()) iter::successors(Some(token), |token| token.prev_token())
} }
/// Soft-deprecated in favor of mutable tree editing API `edit_in_place::Ident`.
pub trait AstNodeEdit: AstNode + Clone + Sized { pub trait AstNodeEdit: AstNode + Clone + Sized {
fn indent_level(&self) -> IndentLevel { fn indent_level(&self) -> IndentLevel {
IndentLevel::from_node(self.syntax()) IndentLevel::from_node(self.syntax())
} }
#[must_use] #[must_use]
fn indent(&self, level: IndentLevel) -> Self { fn indent(&self, level: IndentLevel) -> Self {
fn indent_inner(node: &SyntaxNode, level: IndentLevel) -> SyntaxNode { Self::cast(level.clone_increase_indent(self.syntax())).unwrap()
let res = node.clone_subtree().clone_for_update(); }
level.increase_indent(&res); #[must_use]
res.clone_subtree() fn indent_with_mapping(&self, level: IndentLevel, make: &SyntaxFactory) -> Self {
let new_node = self.indent(level);
if let Some(mut mapping) = make.mappings() {
let mut builder = SyntaxMappingBuilder::new(new_node.syntax().clone());
for (old, new) in self.syntax().children().zip(new_node.syntax().children()) {
builder.map_node(old, new);
}
builder.finish(&mut mapping);
} }
new_node
Self::cast(indent_inner(self.syntax(), level)).unwrap()
} }
#[must_use] #[must_use]
fn dedent(&self, level: IndentLevel) -> Self { fn dedent(&self, level: IndentLevel) -> Self {
fn dedent_inner(node: &SyntaxNode, level: IndentLevel) -> SyntaxNode { Self::cast(level.clone_decrease_indent(self.syntax())).unwrap()
let res = node.clone_subtree().clone_for_update();
level.decrease_indent(&res);
res.clone_subtree()
}
Self::cast(dedent_inner(self.syntax(), level)).unwrap()
} }
#[must_use] #[must_use]
fn reset_indent(&self) -> Self { fn reset_indent(&self) -> Self {

View file

@ -644,7 +644,7 @@ impl Removable for ast::Use {
impl ast::Impl { impl ast::Impl {
pub fn get_or_create_assoc_item_list(&self) -> ast::AssocItemList { pub fn get_or_create_assoc_item_list(&self) -> ast::AssocItemList {
if self.assoc_item_list().is_none() { if self.assoc_item_list().is_none() {
let assoc_item_list = make::assoc_item_list().clone_for_update(); let assoc_item_list = make::assoc_item_list(None).clone_for_update();
ted::append_child(self.syntax(), assoc_item_list.syntax()); ted::append_child(self.syntax(), assoc_item_list.syntax());
} }
self.assoc_item_list().unwrap() self.assoc_item_list().unwrap()

View file

@ -377,22 +377,13 @@ impl CastExpr {
#[inline] #[inline]
pub fn as_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![as]) } pub fn as_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![as]) }
} }
pub struct ClosureBinder {
pub(crate) syntax: SyntaxNode,
}
impl ClosureBinder {
#[inline]
pub fn generic_param_list(&self) -> Option<GenericParamList> { support::child(&self.syntax) }
#[inline]
pub fn for_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![for]) }
}
pub struct ClosureExpr { pub struct ClosureExpr {
pub(crate) syntax: SyntaxNode, pub(crate) syntax: SyntaxNode,
} }
impl ast::HasAttrs for ClosureExpr {} impl ast::HasAttrs for ClosureExpr {}
impl ClosureExpr { impl ClosureExpr {
#[inline] #[inline]
pub fn closure_binder(&self) -> Option<ClosureBinder> { support::child(&self.syntax) } pub fn for_binder(&self) -> Option<ForBinder> { support::child(&self.syntax) }
#[inline] #[inline]
pub fn param_list(&self) -> Option<ParamList> { support::child(&self.syntax) } pub fn param_list(&self) -> Option<ParamList> { support::child(&self.syntax) }
#[inline] #[inline]
@ -615,6 +606,15 @@ impl FnPtrType {
#[inline] #[inline]
pub fn unsafe_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![unsafe]) } pub fn unsafe_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![unsafe]) }
} }
pub struct ForBinder {
pub(crate) syntax: SyntaxNode,
}
impl ForBinder {
#[inline]
pub fn generic_param_list(&self) -> Option<GenericParamList> { support::child(&self.syntax) }
#[inline]
pub fn for_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![for]) }
}
pub struct ForExpr { pub struct ForExpr {
pub(crate) syntax: SyntaxNode, pub(crate) syntax: SyntaxNode,
} }
@ -632,11 +632,9 @@ pub struct ForType {
} }
impl ForType { impl ForType {
#[inline] #[inline]
pub fn generic_param_list(&self) -> Option<GenericParamList> { support::child(&self.syntax) } pub fn for_binder(&self) -> Option<ForBinder> { support::child(&self.syntax) }
#[inline] #[inline]
pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) } pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
#[inline]
pub fn for_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![for]) }
} }
pub struct FormatArgsArg { pub struct FormatArgsArg {
pub(crate) syntax: SyntaxNode, pub(crate) syntax: SyntaxNode,
@ -1765,6 +1763,8 @@ pub struct TypeBound {
pub(crate) syntax: SyntaxNode, pub(crate) syntax: SyntaxNode,
} }
impl TypeBound { impl TypeBound {
#[inline]
pub fn for_binder(&self) -> Option<ForBinder> { support::child(&self.syntax) }
#[inline] #[inline]
pub fn lifetime(&self) -> Option<Lifetime> { support::child(&self.syntax) } pub fn lifetime(&self) -> Option<Lifetime> { support::child(&self.syntax) }
#[inline] #[inline]
@ -1938,13 +1938,11 @@ pub struct WherePred {
impl ast::HasTypeBounds for WherePred {} impl ast::HasTypeBounds for WherePred {}
impl WherePred { impl WherePred {
#[inline] #[inline]
pub fn generic_param_list(&self) -> Option<GenericParamList> { support::child(&self.syntax) } pub fn for_binder(&self) -> Option<ForBinder> { support::child(&self.syntax) }
#[inline] #[inline]
pub fn lifetime(&self) -> Option<Lifetime> { support::child(&self.syntax) } pub fn lifetime(&self) -> Option<Lifetime> { support::child(&self.syntax) }
#[inline] #[inline]
pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) } pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
#[inline]
pub fn for_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![for]) }
} }
pub struct WhileExpr { pub struct WhileExpr {
pub(crate) syntax: SyntaxNode, pub(crate) syntax: SyntaxNode,
@ -3239,42 +3237,6 @@ impl fmt::Debug for CastExpr {
f.debug_struct("CastExpr").field("syntax", &self.syntax).finish() f.debug_struct("CastExpr").field("syntax", &self.syntax).finish()
} }
} }
impl AstNode for ClosureBinder {
#[inline]
fn kind() -> SyntaxKind
where
Self: Sized,
{
CLOSURE_BINDER
}
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == CLOSURE_BINDER }
#[inline]
fn cast(syntax: SyntaxNode) -> Option<Self> {
if Self::can_cast(syntax.kind()) {
Some(Self { syntax })
} else {
None
}
}
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl hash::Hash for ClosureBinder {
fn hash<H: hash::Hasher>(&self, state: &mut H) { self.syntax.hash(state); }
}
impl Eq for ClosureBinder {}
impl PartialEq for ClosureBinder {
fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax }
}
impl Clone for ClosureBinder {
fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } }
}
impl fmt::Debug for ClosureBinder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ClosureBinder").field("syntax", &self.syntax).finish()
}
}
impl AstNode for ClosureExpr { impl AstNode for ClosureExpr {
#[inline] #[inline]
fn kind() -> SyntaxKind fn kind() -> SyntaxKind
@ -3815,6 +3777,42 @@ impl fmt::Debug for FnPtrType {
f.debug_struct("FnPtrType").field("syntax", &self.syntax).finish() f.debug_struct("FnPtrType").field("syntax", &self.syntax).finish()
} }
} }
impl AstNode for ForBinder {
#[inline]
fn kind() -> SyntaxKind
where
Self: Sized,
{
FOR_BINDER
}
#[inline]
fn can_cast(kind: SyntaxKind) -> bool { kind == FOR_BINDER }
#[inline]
fn cast(syntax: SyntaxNode) -> Option<Self> {
if Self::can_cast(syntax.kind()) {
Some(Self { syntax })
} else {
None
}
}
#[inline]
fn syntax(&self) -> &SyntaxNode { &self.syntax }
}
impl hash::Hash for ForBinder {
fn hash<H: hash::Hasher>(&self, state: &mut H) { self.syntax.hash(state); }
}
impl Eq for ForBinder {}
impl PartialEq for ForBinder {
fn eq(&self, other: &Self) -> bool { self.syntax == other.syntax }
}
impl Clone for ForBinder {
fn clone(&self) -> Self { Self { syntax: self.syntax.clone() } }
}
impl fmt::Debug for ForBinder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ForBinder").field("syntax", &self.syntax).finish()
}
}
impl AstNode for ForExpr { impl AstNode for ForExpr {
#[inline] #[inline]
fn kind() -> SyntaxKind fn kind() -> SyntaxKind
@ -10146,11 +10144,6 @@ impl std::fmt::Display for CastExpr {
std::fmt::Display::fmt(self.syntax(), f) std::fmt::Display::fmt(self.syntax(), f)
} }
} }
impl std::fmt::Display for ClosureBinder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for ClosureExpr { impl std::fmt::Display for ClosureExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f) std::fmt::Display::fmt(self.syntax(), f)
@ -10226,6 +10219,11 @@ impl std::fmt::Display for FnPtrType {
std::fmt::Display::fmt(self.syntax(), f) std::fmt::Display::fmt(self.syntax(), f)
} }
} }
impl std::fmt::Display for ForBinder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for ForExpr { impl std::fmt::Display for ForExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f) std::fmt::Display::fmt(self.syntax(), f)

View file

@ -229,8 +229,18 @@ pub fn ty_fn_ptr<I: Iterator<Item = Param>>(
} }
} }
pub fn assoc_item_list() -> ast::AssocItemList { pub fn assoc_item_list(
ast_from_text("impl C for D {}") body: Option<Vec<either::Either<ast::Attr, ast::AssocItem>>>,
) -> ast::AssocItemList {
let is_break_braces = body.is_some();
let body_newline = if is_break_braces { "\n".to_owned() } else { String::new() };
let body_indent = if is_break_braces { " ".to_owned() } else { String::new() };
let body = match body {
Some(bd) => bd.iter().map(|elem| elem.to_string()).join("\n\n "),
None => String::new(),
};
ast_from_text(&format!("impl C for D {{{body_newline}{body_indent}{body}{body_newline}}}"))
} }
fn merge_gen_params( fn merge_gen_params(
@ -273,7 +283,7 @@ pub fn impl_(
generic_args: Option<ast::GenericArgList>, generic_args: Option<ast::GenericArgList>,
path_type: ast::Type, path_type: ast::Type,
where_clause: Option<ast::WhereClause>, where_clause: Option<ast::WhereClause>,
body: Option<Vec<either::Either<ast::Attr, ast::AssocItem>>>, body: Option<ast::AssocItemList>,
) -> ast::Impl { ) -> ast::Impl {
let gen_args = generic_args.map_or_else(String::new, |it| it.to_string()); let gen_args = generic_args.map_or_else(String::new, |it| it.to_string());
@ -281,20 +291,13 @@ pub fn impl_(
let body_newline = let body_newline =
if where_clause.is_some() && body.is_none() { "\n".to_owned() } else { String::new() }; if where_clause.is_some() && body.is_none() { "\n".to_owned() } else { String::new() };
let where_clause = match where_clause { let where_clause = match where_clause {
Some(pr) => format!("\n{pr}\n"), Some(pr) => format!("\n{pr}\n"),
None => " ".to_owned(), None => " ".to_owned(),
}; };
let body = match body { let body = body.map_or_else(|| format!("{{{body_newline}}}"), |it| it.to_string());
Some(bd) => bd.iter().map(|elem| elem.to_string()).join(""), ast_from_text(&format!("impl{gen_params} {path_type}{gen_args}{where_clause}{body}"))
None => String::new(),
};
ast_from_text(&format!(
"impl{gen_params} {path_type}{gen_args}{where_clause}{{{body_newline}{body}}}"
))
} }
pub fn impl_trait( pub fn impl_trait(
@ -308,7 +311,7 @@ pub fn impl_trait(
ty: ast::Type, ty: ast::Type,
trait_where_clause: Option<ast::WhereClause>, trait_where_clause: Option<ast::WhereClause>,
ty_where_clause: Option<ast::WhereClause>, ty_where_clause: Option<ast::WhereClause>,
body: Option<Vec<either::Either<ast::Attr, ast::AssocItem>>>, body: Option<ast::AssocItemList>,
) -> ast::Impl { ) -> ast::Impl {
let is_unsafe = if is_unsafe { "unsafe " } else { "" }; let is_unsafe = if is_unsafe { "unsafe " } else { "" };
@ -330,13 +333,10 @@ pub fn impl_trait(
let where_clause = merge_where_clause(ty_where_clause, trait_where_clause) let where_clause = merge_where_clause(ty_where_clause, trait_where_clause)
.map_or_else(|| " ".to_owned(), |wc| format!("\n{wc}\n")); .map_or_else(|| " ".to_owned(), |wc| format!("\n{wc}\n"));
let body = match body { let body = body.map_or_else(|| format!("{{{body_newline}}}"), |it| it.to_string());
Some(bd) => bd.iter().map(|elem| elem.to_string()).join(""),
None => String::new(),
};
ast_from_text(&format!( ast_from_text(&format!(
"{is_unsafe}impl{gen_params} {is_negative}{path_type}{trait_gen_args} for {ty}{type_gen_args}{where_clause}{{{body_newline}{body}}}" "{is_unsafe}impl{gen_params} {is_negative}{path_type}{trait_gen_args} for {ty}{type_gen_args}{where_clause}{body}"
)) ))
} }

View file

@ -805,9 +805,7 @@ impl ast::SelfParam {
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum TypeBoundKind { pub enum TypeBoundKind {
/// Trait /// Trait
PathType(ast::PathType), PathType(Option<ast::ForBinder>, ast::PathType),
/// for<'a> ...
ForType(ast::ForType),
/// use /// use
Use(ast::UseBoundGenericArgs), Use(ast::UseBoundGenericArgs),
/// 'a /// 'a
@ -817,9 +815,7 @@ pub enum TypeBoundKind {
impl ast::TypeBound { impl ast::TypeBound {
pub fn kind(&self) -> TypeBoundKind { pub fn kind(&self) -> TypeBoundKind {
if let Some(path_type) = support::children(self.syntax()).next() { if let Some(path_type) = support::children(self.syntax()).next() {
TypeBoundKind::PathType(path_type) TypeBoundKind::PathType(self.for_binder(), path_type)
} else if let Some(for_type) = support::children(self.syntax()).next() {
TypeBoundKind::ForType(for_type)
} else if let Some(args) = self.use_bound_generic_args() { } else if let Some(args) = self.use_bound_generic_args() {
TypeBoundKind::Use(args) TypeBoundKind::Use(args)
} else if let Some(lifetime) = self.lifetime() { } else if let Some(lifetime) = self.lifetime() {

View file

@ -38,7 +38,7 @@ impl SyntaxFactory {
self.mappings.as_ref().map(|mappings| mappings.take()).unwrap_or_default() self.mappings.as_ref().map(|mappings| mappings.take()).unwrap_or_default()
} }
fn mappings(&self) -> Option<RefMut<'_, SyntaxMapping>> { pub(crate) fn mappings(&self) -> Option<RefMut<'_, SyntaxMapping>> {
self.mappings.as_ref().map(|it| it.borrow_mut()) self.mappings.as_ref().map(|it| it.borrow_mut())
} }
} }

View file

@ -5,7 +5,7 @@
//! [`SyntaxEditor`]: https://github.com/dotnet/roslyn/blob/43b0b05cc4f492fd5de00f6f6717409091df8daa/src/Workspaces/Core/Portable/Editing/SyntaxEditor.cs //! [`SyntaxEditor`]: https://github.com/dotnet/roslyn/blob/43b0b05cc4f492fd5de00f6f6717409091df8daa/src/Workspaces/Core/Portable/Editing/SyntaxEditor.cs
use std::{ use std::{
fmt, fmt, iter,
num::NonZeroU32, num::NonZeroU32,
ops::RangeInclusive, ops::RangeInclusive,
sync::atomic::{AtomicU32, Ordering}, sync::atomic::{AtomicU32, Ordering},
@ -41,6 +41,15 @@ impl SyntaxEditor {
self.annotations.push((element.syntax_element(), annotation)) self.annotations.push((element.syntax_element(), annotation))
} }
pub fn add_annotation_all(
&mut self,
elements: Vec<impl Element>,
annotation: SyntaxAnnotation,
) {
self.annotations
.extend(elements.into_iter().map(|e| e.syntax_element()).zip(iter::repeat(annotation)));
}
pub fn merge(&mut self, mut other: SyntaxEditor) { pub fn merge(&mut self, mut other: SyntaxEditor) {
debug_assert!( debug_assert!(
self.root == other.root || other.root.ancestors().any(|node| node == self.root), self.root == other.root || other.root.ancestors().any(|node| node == self.root),

View file

@ -92,6 +92,42 @@ fn get_or_insert_comma_after(editor: &mut SyntaxEditor, syntax: &SyntaxNode) ->
} }
} }
impl ast::AssocItemList {
/// Adds a new associated item after all of the existing associated items.
///
/// Attention! This function does align the first line of `item` with respect to `self`,
/// but it does _not_ change indentation of other lines (if any).
pub fn add_items(&self, editor: &mut SyntaxEditor, items: Vec<ast::AssocItem>) {
let (indent, position, whitespace) = match self.assoc_items().last() {
Some(last_item) => (
IndentLevel::from_node(last_item.syntax()),
Position::after(last_item.syntax()),
"\n\n",
),
None => match self.l_curly_token() {
Some(l_curly) => {
normalize_ws_between_braces(editor, self.syntax());
(IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly), "\n")
}
None => (IndentLevel::single(), Position::last_child_of(self.syntax()), "\n"),
},
};
let elements: Vec<SyntaxElement> = items
.into_iter()
.enumerate()
.flat_map(|(i, item)| {
let whitespace = if i != 0 { "\n\n" } else { whitespace };
vec![
make::tokens::whitespace(&format!("{whitespace}{indent}")).into(),
item.syntax().clone().into(),
]
})
.collect();
editor.insert_all(position, elements);
}
}
impl ast::VariantList { impl ast::VariantList {
pub fn add_variant(&self, editor: &mut SyntaxEditor, variant: &ast::Variant) { pub fn add_variant(&self, editor: &mut SyntaxEditor, variant: &ast::Variant) {
let make = SyntaxFactory::without_mappings(); let make = SyntaxFactory::without_mappings();

View file

@ -252,18 +252,8 @@ Release steps:
4. Commit & push the changelog. 4. Commit & push the changelog.
5. Run `cargo xtask publish-release-notes <CHANGELOG>` -- this will convert the changelog entry in AsciiDoc to Markdown and update the body of GitHub Releases entry. 5. Run `cargo xtask publish-release-notes <CHANGELOG>` -- this will convert the changelog entry in AsciiDoc to Markdown and update the body of GitHub Releases entry.
6. Tweet. 6. Tweet.
7. Make a new branch and run `cargo xtask rustc-pull`, open a PR, and merge it. 7. Perform a subtree [pull](#performing-a-pull).
This will pull any changes from `rust-lang/rust` into `rust-analyzer`. 8. Perform a subtree [push](#performing-a-push).
8. Switch to `master`, pull, then run `cargo xtask rustc-push --rust-path ../rust-rust-analyzer --rust-fork matklad/rust`.
Replace `matklad/rust` with your own fork of `rust-lang/rust`.
You can use the token to authenticate when you get prompted for a password, since `josh` will push over HTTPS, not SSH.
This will push the `rust-analyzer` changes to your fork.
You can then open a PR against `rust-lang/rust`.
Note: besides the `rust-rust-analyzer` clone, the Josh cache (stored under `~/.cache/rust-analyzer-josh`) will contain a bare clone of `rust-lang/rust`.
This currently takes about 3.5 GB.
This [HackMD](https://hackmd.io/7pOuxnkdQDaL1Y1FQr65xg) has details about how `josh` syncs work.
If the GitHub Actions release fails because of a transient problem like a timeout, you can re-run the job from the Actions console. If the GitHub Actions release fails because of a transient problem like a timeout, you can re-run the job from the Actions console.
If it fails because of something that needs to be fixed, remove the release tag (if needed), fix the problem, then start over. If it fails because of something that needs to be fixed, remove the release tag (if needed), fix the problem, then start over.
@ -288,3 +278,43 @@ There are two sets of people with extra permissions:
If you don't feel like reviewing for whatever reason, someone else will pick the review up (but please speak up if you don't feel like it)! If you don't feel like reviewing for whatever reason, someone else will pick the review up (but please speak up if you don't feel like it)!
* The [rust-lang](https://github.com/rust-lang) team [t-rust-analyzer-contributors]([https://github.com/orgs/rust-analyzer/teams/triage](https://github.com/rust-lang/team/blob/master/teams/rust-analyzer-contributors.toml)). * The [rust-lang](https://github.com/rust-lang) team [t-rust-analyzer-contributors]([https://github.com/orgs/rust-analyzer/teams/triage](https://github.com/rust-lang/team/blob/master/teams/rust-analyzer-contributors.toml)).
This team has general triaging permissions allowing to label, close and re-open issues. This team has general triaging permissions allowing to label, close and re-open issues.
## Synchronizing subtree changes
`rust-analyzer` is a [josh](https://josh-project.github.io/josh/intro.html) subtree of the [rust-lang/rust](https://github.com/rust-lang/rust)
repository. We use the [rustc-josh-sync](https://github.com/rust-lang/josh-sync) tool to perform synchronization between these two
repositories. You can find documentation of the tool [here](https://github.com/rust-lang/josh-sync).
You can install the synchronization tool using the following commands:
```
cargo install --locked --git https://github.com/rust-lang/josh-sync
```
Both pulls (synchronizing changes from rust-lang/rust into rust-analyzer) and pushes (synchronizing
changes from rust-analyzer into rust-lang/rust) are performed from this repository.
changes from rust-analyzer to rust-lang/rust) are performed from this repository.
Usually we first perform a pull, wait for it to be merged, and then perform a push.
### Performing a pull
1) Checkout a new branch that will be used to create a PR against rust-analyzer
2) Run the pull command
```
rustc-josh-sync pull
```
3) Push the branch to your fork of `rust-analyzer` and create a PR
- If you have the `gh` CLI installed, `rustc-josh-sync` can create the PR for you.
### Performing a push
Wait for the previous pull to be merged.
1) Switch to `master` and pull
2) Run the push command to create a branch named `<branch-name>` in a `rustc` fork under the `<gh-username>` account
```
rustc-josh-sync push <branch-name> <gh-username>
```
- The push will ask you to download a checkout of the `rust-lang/rust` repository.
- If you get prompted for a password, see [this](https://github.com/rust-lang/josh-sync?tab=readme-ov-file#git-peculiarities).
3) Create a PR from `<branch-name>` into `rust-lang/rust`
> Besides the `rust` checkout, the Josh cache (stored under `~/.cache/rustc-josh`) will contain a bare clone of `rust-lang/rust`. This currently takes several GBs.

View file

@ -3336,15 +3336,16 @@
} }
}, },
"node_modules/form-data": { "node_modules/form-data": {
"version": "4.0.2", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"combined-stream": "^1.0.8", "combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0", "es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12" "mime-types": "^2.1.12"
}, },
"engines": { "engines": {

View file

@ -8,10 +8,9 @@ import type { Disposable } from "vscode";
export type RunnableEnvCfgItem = { export type RunnableEnvCfgItem = {
mask?: string; mask?: string;
env: Record<string, string>; env: { [key: string]: { toString(): string } | null };
platform?: string | string[]; platform?: string | string[];
}; };
export type RunnableEnvCfg = Record<string, string> | RunnableEnvCfgItem[];
type ShowStatusBar = "always" | "never" | { documentSelector: vscode.DocumentSelector }; type ShowStatusBar = "always" | "never" | { documentSelector: vscode.DocumentSelector };
@ -261,18 +260,13 @@ export class Config {
return this.get<boolean | undefined>("testExplorer"); return this.get<boolean | undefined>("testExplorer");
} }
runnablesExtraEnv(label: string): Record<string, string> | undefined { runnablesExtraEnv(label: string): Env {
// eslint-disable-next-line @typescript-eslint/no-explicit-any const serverEnv = this.serverExtraEnv;
const item = this.get<any>("runnables.extraEnv") ?? this.get<any>("runnableEnv"); let extraEnv =
if (!item) return undefined; this.get<
// eslint-disable-next-line @typescript-eslint/no-explicit-any RunnableEnvCfgItem[] | { [key: string]: { toString(): string } | null } | null
const fixRecord = (r: Record<string, any>) => { >("runnables.extraEnv") ?? {};
for (const key in r) { if (!extraEnv) return serverEnv;
if (typeof r[key] !== "string") {
r[key] = String(r[key]);
}
}
};
const platform = process.platform; const platform = process.platform;
const checkPlatform = (it: RunnableEnvCfgItem) => { const checkPlatform = (it: RunnableEnvCfgItem) => {
@ -283,19 +277,25 @@ export class Config {
return true; return true;
}; };
if (item instanceof Array) { if (extraEnv instanceof Array) {
const env = {}; const env = {};
for (const it of item) { for (const it of extraEnv) {
const masked = !it.mask || new RegExp(it.mask).test(label); const masked = !it.mask || new RegExp(it.mask).test(label);
if (masked && checkPlatform(it)) { if (masked && checkPlatform(it)) {
Object.assign(env, it.env); Object.assign(env, it.env);
} }
} }
fixRecord(env); extraEnv = env;
return env;
} }
fixRecord(item); const runnableExtraEnv = substituteVariablesInEnv(
return item; Object.fromEntries(
Object.entries(extraEnv).map(([k, v]) => [
k,
typeof v === "string" ? v : v?.toString(),
]),
),
);
return { ...runnableExtraEnv, ...serverEnv };
} }
get restartServerOnConfigChange() { get restartServerOnConfigChange() {

View file

@ -6,7 +6,14 @@ import type * as ra from "./lsp_ext";
import { Cargo } from "./toolchain"; import { Cargo } from "./toolchain";
import type { Ctx } from "./ctx"; import type { Ctx } from "./ctx";
import { createTaskFromRunnable, prepareEnv } from "./run"; import { createTaskFromRunnable, prepareEnv } from "./run";
import { execute, isCargoRunnableArgs, unwrapUndefinable, log, normalizeDriveLetter } from "./util"; import {
execute,
isCargoRunnableArgs,
unwrapUndefinable,
log,
normalizeDriveLetter,
Env,
} from "./util";
import type { Config } from "./config"; import type { Config } from "./config";
// Here we want to keep track on everything that's currently running // Here we want to keep track on everything that's currently running
@ -206,10 +213,7 @@ type SourceFileMap = {
destination: string; destination: string;
}; };
async function discoverSourceFileMap( async function discoverSourceFileMap(env: Env, cwd: string): Promise<SourceFileMap | undefined> {
env: Record<string, string>,
cwd: string,
): Promise<SourceFileMap | undefined> {
const sysroot = env["RUSTC_TOOLCHAIN"]; const sysroot = env["RUSTC_TOOLCHAIN"];
if (sysroot) { if (sysroot) {
// let's try to use the default toolchain // let's try to use the default toolchain
@ -232,7 +236,7 @@ type PropertyFetcher<Config, Input, Key extends keyof Config> = (
type DebugConfigProvider<Type extends string, DebugConfig extends BaseDebugConfig<Type>> = { type DebugConfigProvider<Type extends string, DebugConfig extends BaseDebugConfig<Type>> = {
executableProperty: keyof DebugConfig; executableProperty: keyof DebugConfig;
environmentProperty: PropertyFetcher<DebugConfig, Record<string, string>, keyof DebugConfig>; environmentProperty: PropertyFetcher<DebugConfig, Env, keyof DebugConfig>;
runnableArgsProperty: PropertyFetcher<DebugConfig, ra.CargoRunnableArgs, keyof DebugConfig>; runnableArgsProperty: PropertyFetcher<DebugConfig, ra.CargoRunnableArgs, keyof DebugConfig>;
sourceFileMapProperty?: keyof DebugConfig; sourceFileMapProperty?: keyof DebugConfig;
type: Type; type: Type;
@ -276,7 +280,7 @@ const knownEngines: {
"environment", "environment",
Object.entries(env).map((entry) => ({ Object.entries(env).map((entry) => ({
name: entry[0], name: entry[0],
value: entry[1], value: entry[1] ?? "",
})), })),
], ],
runnableArgsProperty: (runnableArgs: ra.CargoRunnableArgs) => [ runnableArgsProperty: (runnableArgs: ra.CargoRunnableArgs) => [
@ -304,10 +308,7 @@ const knownEngines: {
}, },
}; };
async function getDebugExecutable( async function getDebugExecutable(runnableArgs: ra.CargoRunnableArgs, env: Env): Promise<string> {
runnableArgs: ra.CargoRunnableArgs,
env: Record<string, string>,
): Promise<string> {
const cargo = new Cargo(runnableArgs.workspaceRoot || ".", env); const cargo = new Cargo(runnableArgs.workspaceRoot || ".", env);
const executable = await cargo.executableFromArgs(runnableArgs); const executable = await cargo.executableFromArgs(runnableArgs);
@ -328,7 +329,7 @@ function getDebugConfig(
runnable: ra.Runnable, runnable: ra.Runnable,
runnableArgs: ra.CargoRunnableArgs, runnableArgs: ra.CargoRunnableArgs,
executable: string, executable: string,
env: Record<string, string>, env: Env,
sourceFileMap?: Record<string, string>, sourceFileMap?: Record<string, string>,
): vscode.DebugConfiguration { ): vscode.DebugConfiguration {
const { const {
@ -380,14 +381,14 @@ type CodeLldbDebugConfig = {
args: string[]; args: string[];
sourceMap: Record<string, string> | undefined; sourceMap: Record<string, string> | undefined;
sourceLanguages: ["rust"]; sourceLanguages: ["rust"];
env: Record<string, string>; env: Env;
} & BaseDebugConfig<"lldb">; } & BaseDebugConfig<"lldb">;
type NativeDebugConfig = { type NativeDebugConfig = {
target: string; target: string;
// See https://github.com/WebFreak001/code-debug/issues/359 // See https://github.com/WebFreak001/code-debug/issues/359
arguments: string; arguments: string;
env: Record<string, string>; env: Env;
valuesFormatting: "prettyPrinters"; valuesFormatting: "prettyPrinters";
} & BaseDebugConfig<"gdb">; } & BaseDebugConfig<"gdb">;

View file

@ -7,7 +7,7 @@ import type { CtxInit } from "./ctx";
import { makeDebugConfig } from "./debug"; import { makeDebugConfig } from "./debug";
import type { Config } from "./config"; import type { Config } from "./config";
import type { LanguageClient } from "vscode-languageclient/node"; import type { LanguageClient } from "vscode-languageclient/node";
import { log, unwrapUndefinable, type RustEditor } from "./util"; import { Env, log, unwrapUndefinable, type RustEditor } from "./util";
const quickPickButtons = [ const quickPickButtons = [
{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configuration." }, { iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configuration." },
@ -122,11 +122,8 @@ export class RunnableQuickPick implements vscode.QuickPickItem {
} }
} }
export function prepareBaseEnv( export function prepareBaseEnv(inheritEnv: boolean, base?: Env): Env {
inheritEnv: boolean, const env: Env = { RUST_BACKTRACE: "short" };
base?: Record<string, string>,
): Record<string, string> {
const env: Record<string, string> = { RUST_BACKTRACE: "short" };
if (inheritEnv) { if (inheritEnv) {
Object.assign(env, process.env); Object.assign(env, process.env);
} }
@ -136,11 +133,7 @@ export function prepareBaseEnv(
return env; return env;
} }
export function prepareEnv( export function prepareEnv(inheritEnv: boolean, runnableEnv?: Env, runnableEnvCfg?: Env): Env {
inheritEnv: boolean,
runnableEnv?: Record<string, string>,
runnableEnvCfg?: Record<string, string>,
): Record<string, string> {
const env = prepareBaseEnv(inheritEnv, runnableEnv); const env = prepareBaseEnv(inheritEnv, runnableEnv);
if (runnableEnvCfg) { if (runnableEnvCfg) {

View file

@ -1,6 +1,7 @@
import * as vscode from "vscode"; import * as vscode from "vscode";
import type { Config } from "./config"; import type { Config } from "./config";
import * as toolchain from "./toolchain"; import * as toolchain from "./toolchain";
import { Env } from "./util";
// This ends up as the `type` key in tasks.json. RLS also uses `cargo` and // This ends up as the `type` key in tasks.json. RLS also uses `cargo` and
// our configuration should be compatible with it so use the same key. // our configuration should be compatible with it so use the same key.
@ -117,8 +118,8 @@ export async function buildRustTask(
export async function targetToExecution( export async function targetToExecution(
definition: TaskDefinition, definition: TaskDefinition,
options?: { options?: {
env?: { [key: string]: string };
cwd?: string; cwd?: string;
env?: Env;
}, },
cargo?: string, cargo?: string,
): Promise<vscode.ProcessExecution | vscode.ShellExecution> { ): Promise<vscode.ProcessExecution | vscode.ShellExecution> {
@ -131,7 +132,12 @@ export async function targetToExecution(
command = definition.command; command = definition.command;
args = definition.args || []; args = definition.args || [];
} }
return new vscode.ProcessExecution(command, args, options); return new vscode.ProcessExecution(command, args, {
cwd: options?.cwd,
env: Object.fromEntries(
Object.entries(options?.env ?? {}).map(([key, value]) => [key, value ?? ""]),
),
});
} }
export function activateTaskProvider(config: Config): vscode.Disposable { export function activateTaskProvider(config: Config): vscode.Disposable {

View file

@ -3,7 +3,7 @@ import * as os from "os";
import * as path from "path"; import * as path from "path";
import * as readline from "readline"; import * as readline from "readline";
import * as vscode from "vscode"; import * as vscode from "vscode";
import { log, memoizeAsync, unwrapUndefinable } from "./util"; import { Env, log, memoizeAsync, unwrapUndefinable } from "./util";
import type { CargoRunnableArgs } from "./lsp_ext"; import type { CargoRunnableArgs } from "./lsp_ext";
interface CompilationArtifact { interface CompilationArtifact {
@ -37,7 +37,7 @@ interface CompilerMessage {
export class Cargo { export class Cargo {
constructor( constructor(
readonly rootFolder: string, readonly rootFolder: string,
readonly env: Record<string, string>, readonly env: Env,
) {} ) {}
// Made public for testing purposes // Made public for testing purposes
@ -156,7 +156,7 @@ export class Cargo {
/** Mirrors `toolchain::cargo()` implementation */ /** Mirrors `toolchain::cargo()` implementation */
// FIXME: The server should provide this // FIXME: The server should provide this
export function cargoPath(env?: Record<string, string>): Promise<string> { export function cargoPath(env?: Env): Promise<string> {
if (env?.["RUSTC_TOOLCHAIN"]) { if (env?.["RUSTC_TOOLCHAIN"]) {
return Promise.resolve("cargo"); return Promise.resolve("cargo");
} }

View file

@ -0,0 +1,2 @@
repo = "rust-analyzer"
filter = ":rev(55d9a533b309119c8acd13061581b43ae8840823:prefix=src/tools/rust-analyzer):/src/tools/rust-analyzer"

View file

@ -1 +1 @@
e05ab47e6c418fb2b9faa2eae9a7e70c65c98eaa 733dab558992d902d6d17576de1da768094e2cf3

View file

@ -17,6 +17,7 @@ exclude_titles = [ # exclude syncs from subtree in rust-lang/rust
"sync from downstream", "sync from downstream",
"Sync from rust", "Sync from rust",
"sync from rust", "sync from rust",
"Rustc pull update",
] ]
labels = ["has-merge-commits", "S-waiting-on-author"] labels = ["has-merge-commits", "S-waiting-on-author"]
@ -27,3 +28,6 @@ labels = ["has-merge-commits", "S-waiting-on-author"]
# Prevents mentions in commits to avoid users being spammed # Prevents mentions in commits to avoid users being spammed
[no-mentions] [no-mentions]
# Automatically close and reopen PRs made by bots to run CI on them
[bot-pull-requests]

View file

@ -8,7 +8,6 @@ rust-version.workspace = true
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
directories = "6.0"
flate2 = "1.1.2" flate2 = "1.1.2"
write-json = "0.1.4" write-json = "0.1.4"
xshell.workspace = true xshell.workspace = true

View file

@ -59,20 +59,6 @@ xflags::xflags! {
optional --dry-run optional --dry-run
} }
cmd rustc-pull {
/// rustc commit to pull.
optional --commit refspec: String
}
cmd rustc-push {
/// rust local path, e.g. `../rust-rust-analyzer`.
required --rust-path rust_path: String
/// rust fork name, e.g. `matklad/rust`.
required --rust-fork rust_fork: String
/// branch name.
optional --branch branch: String
}
cmd dist { cmd dist {
/// Use mimalloc allocator for server /// Use mimalloc allocator for server
optional --mimalloc optional --mimalloc
@ -121,8 +107,6 @@ pub enum XtaskCmd {
Install(Install), Install(Install),
FuzzTests(FuzzTests), FuzzTests(FuzzTests),
Release(Release), Release(Release),
RustcPull(RustcPull),
RustcPush(RustcPush),
Dist(Dist), Dist(Dist),
PublishReleaseNotes(PublishReleaseNotes), PublishReleaseNotes(PublishReleaseNotes),
Metrics(Metrics), Metrics(Metrics),
@ -151,18 +135,6 @@ pub struct Release {
pub dry_run: bool, pub dry_run: bool,
} }
#[derive(Debug)]
pub struct RustcPull {
pub commit: Option<String>,
}
#[derive(Debug)]
pub struct RustcPush {
pub rust_path: String,
pub rust_fork: String,
pub branch: Option<String>,
}
#[derive(Debug)] #[derive(Debug)]
pub struct Dist { pub struct Dist {
pub mimalloc: bool, pub mimalloc: bool,

View file

@ -42,8 +42,6 @@ fn main() -> anyhow::Result<()> {
flags::XtaskCmd::Install(cmd) => cmd.run(sh), flags::XtaskCmd::Install(cmd) => cmd.run(sh),
flags::XtaskCmd::FuzzTests(_) => run_fuzzer(sh), flags::XtaskCmd::FuzzTests(_) => run_fuzzer(sh),
flags::XtaskCmd::Release(cmd) => cmd.run(sh), flags::XtaskCmd::Release(cmd) => cmd.run(sh),
flags::XtaskCmd::RustcPull(cmd) => cmd.run(sh),
flags::XtaskCmd::RustcPush(cmd) => cmd.run(sh),
flags::XtaskCmd::Dist(cmd) => cmd.run(sh), flags::XtaskCmd::Dist(cmd) => cmd.run(sh),
flags::XtaskCmd::PublishReleaseNotes(cmd) => cmd.run(sh), flags::XtaskCmd::PublishReleaseNotes(cmd) => cmd.run(sh),
flags::XtaskCmd::Metrics(cmd) => cmd.run(sh), flags::XtaskCmd::Metrics(cmd) => cmd.run(sh),

View file

@ -1,12 +1,5 @@
mod changelog; mod changelog;
use std::process::{Command, Stdio};
use std::thread;
use std::time::Duration;
use anyhow::{Context as _, bail};
use directories::ProjectDirs;
use stdx::JodChild;
use xshell::{Shell, cmd}; use xshell::{Shell, cmd};
use crate::{date_iso, flags, is_release_tag, project_root}; use crate::{date_iso, flags, is_release_tag, project_root};
@ -59,171 +52,3 @@ impl flags::Release {
Ok(()) Ok(())
} }
} }
// git sync implementation adapted from https://github.com/rust-lang/miri/blob/62039ac/miri-script/src/commands.rs
impl flags::RustcPull {
pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> {
sh.change_dir(project_root());
let commit = self.commit.map(Result::Ok).unwrap_or_else(|| {
let rust_repo_head =
cmd!(sh, "git ls-remote https://github.com/rust-lang/rust/ HEAD").read()?;
rust_repo_head
.split_whitespace()
.next()
.map(|front| front.trim().to_owned())
.ok_or_else(|| anyhow::format_err!("Could not obtain Rust repo HEAD from remote."))
})?;
// Make sure the repo is clean.
if !cmd!(sh, "git status --untracked-files=no --porcelain").read()?.is_empty() {
bail!("working directory must be clean before running `cargo xtask pull`");
}
// This should not add any new root commits. So count those before and after merging.
let num_roots = || -> anyhow::Result<u32> {
Ok(cmd!(sh, "git rev-list HEAD --max-parents=0 --count")
.read()
.context("failed to determine the number of root commits")?
.parse::<u32>()?)
};
let num_roots_before = num_roots()?;
// Make sure josh is running.
let josh = start_josh()?;
// Update rust-version file. As a separate commit, since making it part of
// the merge has confused the heck out of josh in the past.
// We pass `--no-verify` to avoid running any git hooks that might exist,
// in case they dirty the repository.
sh.write_file("rust-version", format!("{commit}\n"))?;
const PREPARING_COMMIT_MESSAGE: &str = "Preparing for merge from rust-lang/rust";
cmd!(sh, "git commit rust-version --no-verify -m {PREPARING_COMMIT_MESSAGE}")
.run()
.context("FAILED to commit rust-version file, something went wrong")?;
// Fetch given rustc commit.
cmd!(sh, "git fetch http://localhost:{JOSH_PORT}/rust-lang/rust.git@{commit}{JOSH_FILTER}.git")
.run()
.inspect_err(|_| {
// Try to un-do the previous `git commit`, to leave the repo in the state we found it it.
cmd!(sh, "git reset --hard HEAD^")
.run()
.expect("FAILED to clean up again after failed `git fetch`, sorry for that");
})
.context("FAILED to fetch new commits, something went wrong (committing the rust-version file has been undone)")?;
// Merge the fetched commit.
const MERGE_COMMIT_MESSAGE: &str = "Merge from rust-lang/rust";
cmd!(sh, "git merge FETCH_HEAD --no-verify --no-ff -m {MERGE_COMMIT_MESSAGE}")
.run()
.context("FAILED to merge new commits, something went wrong")?;
// Check that the number of roots did not increase.
if num_roots()? != num_roots_before {
bail!("Josh created a new root commit. This is probably not the history you want.");
}
drop(josh);
Ok(())
}
}
impl flags::RustcPush {
pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> {
let branch = self.branch.as_deref().unwrap_or("sync-from-ra");
let rust_path = self.rust_path;
let rust_fork = self.rust_fork;
sh.change_dir(project_root());
let base = sh.read_file("rust-version")?.trim().to_owned();
// Make sure the repo is clean.
if !cmd!(sh, "git status --untracked-files=no --porcelain").read()?.is_empty() {
bail!("working directory must be clean before running `cargo xtask push`");
}
// Make sure josh is running.
let josh = start_josh()?;
// Find a repo we can do our preparation in.
sh.change_dir(rust_path);
// Prepare the branch. Pushing works much better if we use as base exactly
// the commit that we pulled from last time, so we use the `rust-version`
// file to find out which commit that would be.
println!("Preparing {rust_fork} (base: {base})...");
if cmd!(sh, "git fetch https://github.com/{rust_fork} {branch}")
.ignore_stderr()
.read()
.is_ok()
{
bail!(
"The branch `{branch}` seems to already exist in `https://github.com/{rust_fork}`. Please delete it and try again."
);
}
cmd!(sh, "git fetch https://github.com/rust-lang/rust {base}").run()?;
cmd!(sh, "git push https://github.com/{rust_fork} {base}:refs/heads/{branch}")
.ignore_stdout()
.ignore_stderr() // silence the "create GitHub PR" message
.run()?;
println!();
// Do the actual push.
sh.change_dir(project_root());
println!("Pushing rust-analyzer changes...");
cmd!(
sh,
"git push http://localhost:{JOSH_PORT}/{rust_fork}.git{JOSH_FILTER}.git HEAD:{branch}"
)
.run()?;
println!();
// Do a round-trip check to make sure the push worked as expected.
cmd!(
sh,
"git fetch http://localhost:{JOSH_PORT}/{rust_fork}.git{JOSH_FILTER}.git {branch}"
)
.ignore_stderr()
.read()?;
let head = cmd!(sh, "git rev-parse HEAD").read()?;
let fetch_head = cmd!(sh, "git rev-parse FETCH_HEAD").read()?;
if head != fetch_head {
bail!(
"Josh created a non-roundtrip push! Do NOT merge this into rustc!\n\
Expected {head}, got {fetch_head}."
);
}
println!(
"Confirmed that the push round-trips back to rust-analyzer properly. Please create a rustc PR:"
);
// https://github.com/github-linguist/linguist/compare/master...octocat:linguist:master
let fork_path = rust_fork.replace('/', ":");
println!(
" https://github.com/rust-lang/rust/compare/{fork_path}:{branch}?quick_pull=1&title=Subtree+update+of+rust-analyzer&body=r?+@ghost"
);
drop(josh);
Ok(())
}
}
/// Used for rustc syncs.
const JOSH_FILTER: &str = ":rev(55d9a533b309119c8acd13061581b43ae8840823:prefix=src/tools/rust-analyzer):/src/tools/rust-analyzer";
const JOSH_PORT: &str = "42042";
fn start_josh() -> anyhow::Result<impl Drop> {
// Determine cache directory.
let local_dir = {
let user_dirs = ProjectDirs::from("org", "rust-lang", "rust-analyzer-josh").unwrap();
user_dirs.cache_dir().to_owned()
};
// Start josh, silencing its output.
let mut cmd = Command::new("josh-proxy");
cmd.arg("--local").arg(local_dir);
cmd.arg("--remote").arg("https://github.com");
cmd.arg("--port").arg(JOSH_PORT);
cmd.arg("--no-background");
cmd.stdout(Stdio::null());
cmd.stderr(Stdio::null());
let josh = cmd.spawn().context("failed to start josh-proxy, make sure it is installed")?;
// Give it some time so hopefully the port is open. (100ms was not enough.)
thread::sleep(Duration::from_millis(200));
Ok(JodChild(josh))
}