Auto merge of #104236 - compiler-errors:rollup-adjshd6, r=compiler-errors
Rollup of 9 pull requests Successful merges: - #102763 (Some diagnostic-related nits) - #103443 (Parser: Recover from using colon as path separator in imports) - #103675 (remove redundent "<>" for ty::Slice with reference type) - #104046 (bootstrap: add support for running Miri on a file) - #104115 (Migrate crate-search element to CSS variables) - #104190 (Ignore "Change InferCtxtBuilder from enter to build" in git blame) - #104201 (Add check in GUI test for file loading failure) - #104211 (⬆️ rust-analyzer) - #104231 (Update mailmap) Failed merges: - #104169 (Migrate `:target` rules to use CSS variables) r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
01a6f30324
108 changed files with 1633 additions and 563 deletions
|
@ -4,3 +4,5 @@ a06baa56b95674fc626b3c3fd680d6a65357fe60
|
||||||
95e00bfed801e264e9c4ac817004153ca0f19eb6
|
95e00bfed801e264e9c4ac817004153ca0f19eb6
|
||||||
# reformat with new rustfmt
|
# reformat with new rustfmt
|
||||||
971c549ca334b7b7406e61e958efcca9c4152822
|
971c549ca334b7b7406e61e958efcca9c4152822
|
||||||
|
# refactor infcx building
|
||||||
|
283abbf0e7d20176f76006825b5c52e9a4234e4c
|
||||||
|
|
2
.mailmap
2
.mailmap
|
@ -217,7 +217,7 @@ Hsiang-Cheng Yang <rick68@users.noreply.github.com>
|
||||||
Ian Jackson <ijackson@chiark.greenend.org.uk> <ian.jackson@citrix.com>
|
Ian Jackson <ijackson@chiark.greenend.org.uk> <ian.jackson@citrix.com>
|
||||||
Ian Jackson <ijackson@chiark.greenend.org.uk> <ijackson+github@slimy.greenend.org.uk>
|
Ian Jackson <ijackson@chiark.greenend.org.uk> <ijackson+github@slimy.greenend.org.uk>
|
||||||
Ian Jackson <ijackson@chiark.greenend.org.uk> <iwj@xenproject.org>
|
Ian Jackson <ijackson@chiark.greenend.org.uk> <iwj@xenproject.org>
|
||||||
Ibraheem Ahmed <ibrah1440@gmail.com>
|
Ibraheem Ahmed <ibraheem@ibraheem.ca> <ibrah1440@gmail.com>
|
||||||
Ilyong Cho <ilyoan@gmail.com>
|
Ilyong Cho <ilyoan@gmail.com>
|
||||||
inquisitivecrystal <22333129+inquisitivecrystal@users.noreply.github.com>
|
inquisitivecrystal <22333129+inquisitivecrystal@users.noreply.github.com>
|
||||||
Irina Popa <irinagpopa@gmail.com>
|
Irina Popa <irinagpopa@gmail.com>
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use rustc_errors::{
|
use rustc_errors::{Applicability, Diagnostic};
|
||||||
Applicability, Diagnostic, DiagnosticBuilder, EmissionGuarantee, ErrorGuaranteed,
|
|
||||||
};
|
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_hir::intravisit::Visitor;
|
use rustc_hir::intravisit::Visitor;
|
||||||
use rustc_hir::Node;
|
use rustc_hir::Node;
|
||||||
|
@ -629,25 +627,20 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
|
||||||
self.buffer_error(err);
|
self.buffer_error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn suggest_map_index_mut_alternatives(
|
fn suggest_map_index_mut_alternatives(&self, ty: Ty<'tcx>, err: &mut Diagnostic, span: Span) {
|
||||||
&self,
|
|
||||||
ty: Ty<'_>,
|
|
||||||
err: &mut DiagnosticBuilder<'_, ErrorGuaranteed>,
|
|
||||||
span: Span,
|
|
||||||
) {
|
|
||||||
let Some(adt) = ty.ty_adt_def() else { return };
|
let Some(adt) = ty.ty_adt_def() else { return };
|
||||||
let did = adt.did();
|
let did = adt.did();
|
||||||
if self.infcx.tcx.is_diagnostic_item(sym::HashMap, did)
|
if self.infcx.tcx.is_diagnostic_item(sym::HashMap, did)
|
||||||
|| self.infcx.tcx.is_diagnostic_item(sym::BTreeMap, did)
|
|| self.infcx.tcx.is_diagnostic_item(sym::BTreeMap, did)
|
||||||
{
|
{
|
||||||
struct V<'a, 'b, 'tcx, G: EmissionGuarantee> {
|
struct V<'a, 'tcx> {
|
||||||
assign_span: Span,
|
assign_span: Span,
|
||||||
err: &'a mut DiagnosticBuilder<'b, G>,
|
err: &'a mut Diagnostic,
|
||||||
ty: Ty<'tcx>,
|
ty: Ty<'tcx>,
|
||||||
suggested: bool,
|
suggested: bool,
|
||||||
}
|
}
|
||||||
impl<'a, 'b: 'a, 'hir, 'tcx, G: EmissionGuarantee> Visitor<'hir> for V<'a, 'b, 'tcx, G> {
|
impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
|
||||||
fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'hir>) {
|
fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) {
|
||||||
hir::intravisit::walk_stmt(self, stmt);
|
hir::intravisit::walk_stmt(self, stmt);
|
||||||
let expr = match stmt.kind {
|
let expr = match stmt.kind {
|
||||||
hir::StmtKind::Semi(expr) | hir::StmtKind::Expr(expr) => expr,
|
hir::StmtKind::Semi(expr) | hir::StmtKind::Expr(expr) => expr,
|
||||||
|
@ -705,7 +698,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
|
||||||
),
|
),
|
||||||
(rv.span.shrink_to_hi(), ")".to_string()),
|
(rv.span.shrink_to_hi(), ")".to_string()),
|
||||||
],
|
],
|
||||||
].into_iter(),
|
],
|
||||||
Applicability::MachineApplicable,
|
Applicability::MachineApplicable,
|
||||||
);
|
);
|
||||||
self.suggested = true;
|
self.suggested = true;
|
||||||
|
|
|
@ -742,7 +742,7 @@ impl Diagnostic {
|
||||||
&mut self,
|
&mut self,
|
||||||
sp: Span,
|
sp: Span,
|
||||||
msg: impl Into<SubdiagnosticMessage>,
|
msg: impl Into<SubdiagnosticMessage>,
|
||||||
suggestions: impl Iterator<Item = String>,
|
suggestions: impl IntoIterator<Item = String>,
|
||||||
applicability: Applicability,
|
applicability: Applicability,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.span_suggestions_with_style(
|
self.span_suggestions_with_style(
|
||||||
|
@ -759,11 +759,11 @@ impl Diagnostic {
|
||||||
&mut self,
|
&mut self,
|
||||||
sp: Span,
|
sp: Span,
|
||||||
msg: impl Into<SubdiagnosticMessage>,
|
msg: impl Into<SubdiagnosticMessage>,
|
||||||
suggestions: impl Iterator<Item = String>,
|
suggestions: impl IntoIterator<Item = String>,
|
||||||
applicability: Applicability,
|
applicability: Applicability,
|
||||||
style: SuggestionStyle,
|
style: SuggestionStyle,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
let mut suggestions: Vec<_> = suggestions.collect();
|
let mut suggestions: Vec<_> = suggestions.into_iter().collect();
|
||||||
suggestions.sort();
|
suggestions.sort();
|
||||||
|
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
|
@ -790,10 +790,10 @@ impl Diagnostic {
|
||||||
pub fn multipart_suggestions(
|
pub fn multipart_suggestions(
|
||||||
&mut self,
|
&mut self,
|
||||||
msg: impl Into<SubdiagnosticMessage>,
|
msg: impl Into<SubdiagnosticMessage>,
|
||||||
suggestions: impl Iterator<Item = Vec<(Span, String)>>,
|
suggestions: impl IntoIterator<Item = Vec<(Span, String)>>,
|
||||||
applicability: Applicability,
|
applicability: Applicability,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
let suggestions: Vec<_> = suggestions.collect();
|
let suggestions: Vec<_> = suggestions.into_iter().collect();
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
!(suggestions
|
!(suggestions
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -599,13 +599,13 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
|
||||||
&mut self,
|
&mut self,
|
||||||
sp: Span,
|
sp: Span,
|
||||||
msg: impl Into<SubdiagnosticMessage>,
|
msg: impl Into<SubdiagnosticMessage>,
|
||||||
suggestions: impl Iterator<Item = String>,
|
suggestions: impl IntoIterator<Item = String>,
|
||||||
applicability: Applicability,
|
applicability: Applicability,
|
||||||
) -> &mut Self);
|
) -> &mut Self);
|
||||||
forward!(pub fn multipart_suggestions(
|
forward!(pub fn multipart_suggestions(
|
||||||
&mut self,
|
&mut self,
|
||||||
msg: impl Into<SubdiagnosticMessage>,
|
msg: impl Into<SubdiagnosticMessage>,
|
||||||
suggestions: impl Iterator<Item = Vec<(Span, String)>>,
|
suggestions: impl IntoIterator<Item = Vec<(Span, String)>>,
|
||||||
applicability: Applicability,
|
applicability: Applicability,
|
||||||
) -> &mut Self);
|
) -> &mut Self);
|
||||||
forward!(pub fn span_suggestion_short(
|
forward!(pub fn span_suggestion_short(
|
||||||
|
|
|
@ -1900,6 +1900,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||||
| ty::Str
|
| ty::Str
|
||||||
| ty::Projection(_)
|
| ty::Projection(_)
|
||||||
| ty::Param(_) => format!("{deref_ty}"),
|
| ty::Param(_) => format!("{deref_ty}"),
|
||||||
|
// we need to test something like <&[_]>::len
|
||||||
|
// and Vec::function();
|
||||||
|
// <&[_]>::len doesn't need an extra "<>" between
|
||||||
|
// but for Adt type like Vec::function()
|
||||||
|
// we would suggest <[_]>::function();
|
||||||
|
_ if self.tcx.sess.source_map().span_wrapped_by_angle_bracket(ty.span) => format!("{deref_ty}"),
|
||||||
_ => format!("<{deref_ty}>"),
|
_ => format!("<{deref_ty}>"),
|
||||||
};
|
};
|
||||||
err.span_suggestion_verbose(
|
err.span_suggestion_verbose(
|
||||||
|
|
|
@ -971,6 +971,23 @@ impl<'a> Parser<'a> {
|
||||||
if self.eat(&token::ModSep) {
|
if self.eat(&token::ModSep) {
|
||||||
self.parse_use_tree_glob_or_nested()?
|
self.parse_use_tree_glob_or_nested()?
|
||||||
} else {
|
} else {
|
||||||
|
// Recover from using a colon as path separator.
|
||||||
|
while self.eat_noexpect(&token::Colon) {
|
||||||
|
self.struct_span_err(self.prev_token.span, "expected `::`, found `:`")
|
||||||
|
.span_suggestion_short(
|
||||||
|
self.prev_token.span,
|
||||||
|
"use double colon",
|
||||||
|
"::",
|
||||||
|
Applicability::MachineApplicable,
|
||||||
|
)
|
||||||
|
.note_once("import paths are delimited using `::`")
|
||||||
|
.emit();
|
||||||
|
|
||||||
|
// We parse the rest of the path and append it to the original prefix.
|
||||||
|
self.parse_path_segments(&mut prefix.segments, PathStyle::Mod, None)?;
|
||||||
|
prefix.span = lo.to(self.prev_token.span);
|
||||||
|
}
|
||||||
|
|
||||||
UseTreeKind::Simple(self.parse_rename()?, DUMMY_NODE_ID, DUMMY_NODE_ID)
|
UseTreeKind::Simple(self.parse_rename()?, DUMMY_NODE_ID, DUMMY_NODE_ID)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -401,7 +401,7 @@ impl<'a> Parser<'a> {
|
||||||
.span_suggestions(
|
.span_suggestions(
|
||||||
span.shrink_to_hi(),
|
span.shrink_to_hi(),
|
||||||
"add `mut` or `const` here",
|
"add `mut` or `const` here",
|
||||||
["mut ".to_string(), "const ".to_string()].into_iter(),
|
["mut ".to_string(), "const ".to_string()],
|
||||||
Applicability::HasPlaceholders,
|
Applicability::HasPlaceholders,
|
||||||
)
|
)
|
||||||
.emit();
|
.emit();
|
||||||
|
|
|
@ -87,6 +87,7 @@ use self::VarKind::*;
|
||||||
use rustc_ast::InlineAsmOptions;
|
use rustc_ast::InlineAsmOptions;
|
||||||
use rustc_data_structures::fx::FxIndexMap;
|
use rustc_data_structures::fx::FxIndexMap;
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
|
use rustc_errors::Diagnostic;
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_hir::def::*;
|
use rustc_hir::def::*;
|
||||||
use rustc_hir::def_id::{DefId, LocalDefId};
|
use rustc_hir::def_id::{DefId, LocalDefId};
|
||||||
|
@ -1690,7 +1691,7 @@ impl<'tcx> Liveness<'_, 'tcx> {
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
opt_body: Option<&hir::Body<'_>>,
|
opt_body: Option<&hir::Body<'_>>,
|
||||||
err: &mut rustc_errors::DiagnosticBuilder<'_, ()>,
|
err: &mut Diagnostic,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut has_litstring = false;
|
let mut has_litstring = false;
|
||||||
let Some(opt_body) = opt_body else {return false;};
|
let Some(opt_body) = opt_body else {return false;};
|
||||||
|
|
|
@ -437,7 +437,7 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
|
||||||
|
|
||||||
fn try_lookup_name_relaxed(
|
fn try_lookup_name_relaxed(
|
||||||
&mut self,
|
&mut self,
|
||||||
err: &mut DiagnosticBuilder<'_, ErrorGuaranteed>,
|
err: &mut Diagnostic,
|
||||||
source: PathSource<'_>,
|
source: PathSource<'_>,
|
||||||
path: &[Segment],
|
path: &[Segment],
|
||||||
span: Span,
|
span: Span,
|
||||||
|
@ -497,7 +497,7 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
|
||||||
.contains(span)
|
.contains(span)
|
||||||
{
|
{
|
||||||
// Already reported this issue on the lhs of the type ascription.
|
// Already reported this issue on the lhs of the type ascription.
|
||||||
err.delay_as_bug();
|
err.downgrade_to_delayed_bug();
|
||||||
return (true, candidates);
|
return (true, candidates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -616,7 +616,7 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
|
||||||
|
|
||||||
fn suggest_trait_and_bounds(
|
fn suggest_trait_and_bounds(
|
||||||
&mut self,
|
&mut self,
|
||||||
err: &mut DiagnosticBuilder<'_, ErrorGuaranteed>,
|
err: &mut Diagnostic,
|
||||||
source: PathSource<'_>,
|
source: PathSource<'_>,
|
||||||
res: Option<Res>,
|
res: Option<Res>,
|
||||||
span: Span,
|
span: Span,
|
||||||
|
@ -691,7 +691,7 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
|
||||||
|
|
||||||
fn suggest_typo(
|
fn suggest_typo(
|
||||||
&mut self,
|
&mut self,
|
||||||
err: &mut DiagnosticBuilder<'_, ErrorGuaranteed>,
|
err: &mut Diagnostic,
|
||||||
source: PathSource<'_>,
|
source: PathSource<'_>,
|
||||||
path: &[Segment],
|
path: &[Segment],
|
||||||
span: Span,
|
span: Span,
|
||||||
|
@ -750,7 +750,7 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
|
||||||
|
|
||||||
fn err_code_special_cases(
|
fn err_code_special_cases(
|
||||||
&mut self,
|
&mut self,
|
||||||
err: &mut DiagnosticBuilder<'_, ErrorGuaranteed>,
|
err: &mut Diagnostic,
|
||||||
source: PathSource<'_>,
|
source: PathSource<'_>,
|
||||||
path: &[Segment],
|
path: &[Segment],
|
||||||
span: Span,
|
span: Span,
|
||||||
|
@ -1941,7 +1941,7 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
|
||||||
err.span_suggestions(
|
err.span_suggestions(
|
||||||
span,
|
span,
|
||||||
&msg,
|
&msg,
|
||||||
suggestable_variants.into_iter(),
|
suggestable_variants,
|
||||||
Applicability::MaybeIncorrect,
|
Applicability::MaybeIncorrect,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1995,7 +1995,7 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
|
||||||
err.span_suggestions(
|
err.span_suggestions(
|
||||||
span,
|
span,
|
||||||
msg,
|
msg,
|
||||||
suggestable_variants.into_iter(),
|
suggestable_variants,
|
||||||
Applicability::MaybeIncorrect,
|
Applicability::MaybeIncorrect,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2025,7 +2025,7 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
|
||||||
err.span_suggestions(
|
err.span_suggestions(
|
||||||
span,
|
span,
|
||||||
msg,
|
msg,
|
||||||
suggestable_variants_with_placeholders.into_iter(),
|
suggestable_variants_with_placeholders,
|
||||||
Applicability::HasPlaceholders,
|
Applicability::HasPlaceholders,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -753,6 +753,50 @@ impl SourceMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given a 'Span', tries to tell if the next character is '>'
|
||||||
|
/// and the previous charactoer is '<' after skipping white space
|
||||||
|
/// return true if wrapped by '<>'
|
||||||
|
pub fn span_wrapped_by_angle_bracket(&self, span: Span) -> bool {
|
||||||
|
self.span_to_source(span, |src, start_index, end_index| {
|
||||||
|
if src.get(start_index..end_index).is_none() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
// test the right side to match '>' after skipping white space
|
||||||
|
let end_src = &src[end_index..];
|
||||||
|
let mut i = 0;
|
||||||
|
while let Some(cc) = end_src.chars().nth(i) {
|
||||||
|
if cc == ' ' {
|
||||||
|
i = i + 1;
|
||||||
|
} else if cc == '>' {
|
||||||
|
// found > in the right;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// failed to find '>' return false immediately
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// test the left side to match '<' after skipping white space
|
||||||
|
i = start_index;
|
||||||
|
let start_src = &src[0..start_index];
|
||||||
|
while let Some(cc) = start_src.chars().nth(i) {
|
||||||
|
if cc == ' ' {
|
||||||
|
if i == 0 {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
i = i - 1;
|
||||||
|
} else if cc == '<' {
|
||||||
|
// found < in the left
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// failed to find '<' return false immediately
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(true);
|
||||||
|
})
|
||||||
|
.map_or(false, |is_accessible| is_accessible)
|
||||||
|
}
|
||||||
|
|
||||||
/// Given a `Span`, tries to get a shorter span ending just after the first occurrence of `char`
|
/// Given a `Span`, tries to get a shorter span ending just after the first occurrence of `char`
|
||||||
/// `c`.
|
/// `c`.
|
||||||
pub fn span_through_char(&self, sp: Span, c: char) -> Span {
|
pub fn span_through_char(&self, sp: Span, c: char) -> Span {
|
||||||
|
|
|
@ -1116,7 +1116,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
||||||
err.span_suggestions(
|
err.span_suggestions(
|
||||||
span.shrink_to_lo(),
|
span.shrink_to_lo(),
|
||||||
"consider borrowing here",
|
"consider borrowing here",
|
||||||
["&".to_string(), "&mut ".to_string()].into_iter(),
|
["&".to_string(), "&mut ".to_string()],
|
||||||
Applicability::MaybeIncorrect,
|
Applicability::MaybeIncorrect,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -755,6 +755,7 @@ impl<'a> Builder<'a> {
|
||||||
run::BuildManifest,
|
run::BuildManifest,
|
||||||
run::BumpStage0,
|
run::BumpStage0,
|
||||||
run::ReplaceVersionPlaceholder,
|
run::ReplaceVersionPlaceholder,
|
||||||
|
run::Miri,
|
||||||
),
|
),
|
||||||
// These commands either don't use paths, or they're special-cased in Build::build()
|
// These commands either don't use paths, or they're special-cased in Build::build()
|
||||||
Kind::Clean | Kind::Format | Kind::Setup => vec![],
|
Kind::Clean | Kind::Format | Kind::Setup => vec![],
|
||||||
|
@ -818,7 +819,7 @@ impl<'a> Builder<'a> {
|
||||||
Subcommand::Bench { ref paths, .. } => (Kind::Bench, &paths[..]),
|
Subcommand::Bench { ref paths, .. } => (Kind::Bench, &paths[..]),
|
||||||
Subcommand::Dist { ref paths } => (Kind::Dist, &paths[..]),
|
Subcommand::Dist { ref paths } => (Kind::Dist, &paths[..]),
|
||||||
Subcommand::Install { ref paths } => (Kind::Install, &paths[..]),
|
Subcommand::Install { ref paths } => (Kind::Install, &paths[..]),
|
||||||
Subcommand::Run { ref paths } => (Kind::Run, &paths[..]),
|
Subcommand::Run { ref paths, .. } => (Kind::Run, &paths[..]),
|
||||||
Subcommand::Format { .. } => (Kind::Format, &[][..]),
|
Subcommand::Format { .. } => (Kind::Format, &[][..]),
|
||||||
Subcommand::Clean { .. } | Subcommand::Setup { .. } => {
|
Subcommand::Clean { .. } | Subcommand::Setup { .. } => {
|
||||||
panic!()
|
panic!()
|
||||||
|
|
|
@ -140,6 +140,7 @@ pub enum Subcommand {
|
||||||
},
|
},
|
||||||
Run {
|
Run {
|
||||||
paths: Vec<PathBuf>,
|
paths: Vec<PathBuf>,
|
||||||
|
args: Vec<String>,
|
||||||
},
|
},
|
||||||
Setup {
|
Setup {
|
||||||
profile: Profile,
|
profile: Profile,
|
||||||
|
@ -342,6 +343,9 @@ To learn more about a subcommand, run `./x.py <subcommand> -h`",
|
||||||
Kind::Format => {
|
Kind::Format => {
|
||||||
opts.optflag("", "check", "check formatting instead of applying.");
|
opts.optflag("", "check", "check formatting instead of applying.");
|
||||||
}
|
}
|
||||||
|
Kind::Run => {
|
||||||
|
opts.optmulti("", "args", "arguments for the tool", "ARGS");
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -613,7 +617,7 @@ Arguments:
|
||||||
println!("\nrun requires at least a path!\n");
|
println!("\nrun requires at least a path!\n");
|
||||||
usage(1, &opts, verbose, &subcommand_help);
|
usage(1, &opts, verbose, &subcommand_help);
|
||||||
}
|
}
|
||||||
Subcommand::Run { paths }
|
Subcommand::Run { paths, args: matches.opt_strs("args") }
|
||||||
}
|
}
|
||||||
Kind::Setup => {
|
Kind::Setup => {
|
||||||
let profile = if paths.len() > 1 {
|
let profile = if paths.len() > 1 {
|
||||||
|
@ -721,16 +725,12 @@ impl Subcommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_args(&self) -> Vec<&str> {
|
pub fn test_args(&self) -> Vec<&str> {
|
||||||
let mut args = vec![];
|
|
||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
|
Subcommand::Test { ref test_args, .. } | Subcommand::Bench { ref test_args, .. } => {
|
||||||
args.extend(test_args.iter().flat_map(|s| s.split_whitespace()))
|
test_args.iter().flat_map(|s| s.split_whitespace()).collect()
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => vec![],
|
||||||
}
|
}
|
||||||
|
|
||||||
args
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rustc_args(&self) -> Vec<&str> {
|
pub fn rustc_args(&self) -> Vec<&str> {
|
||||||
|
@ -738,7 +738,16 @@ impl Subcommand {
|
||||||
Subcommand::Test { ref rustc_args, .. } => {
|
Subcommand::Test { ref rustc_args, .. } => {
|
||||||
rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
|
rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
|
||||||
}
|
}
|
||||||
_ => Vec::new(),
|
_ => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn args(&self) -> Vec<&str> {
|
||||||
|
match *self {
|
||||||
|
Subcommand::Run { ref args, .. } => {
|
||||||
|
args.iter().flat_map(|s| s.split_whitespace()).collect()
|
||||||
|
}
|
||||||
|
_ => vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
use crate::builder::{Builder, RunConfig, ShouldRun, Step};
|
|
||||||
use crate::dist::distdir;
|
|
||||||
use crate::tool::Tool;
|
|
||||||
use crate::util::output;
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
|
use crate::builder::{Builder, RunConfig, ShouldRun, Step};
|
||||||
|
use crate::config::TargetSelection;
|
||||||
|
use crate::dist::distdir;
|
||||||
|
use crate::test;
|
||||||
|
use crate::tool::{self, SourceType, Tool};
|
||||||
|
use crate::util::output;
|
||||||
|
use crate::Mode;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ExpandYamlAnchors;
|
pub struct ExpandYamlAnchors;
|
||||||
|
|
||||||
|
@ -125,3 +129,63 @@ impl Step for ReplaceVersionPlaceholder {
|
||||||
builder.run(&mut cmd);
|
builder.run(&mut cmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Miri {
|
||||||
|
stage: u32,
|
||||||
|
host: TargetSelection,
|
||||||
|
target: TargetSelection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Step for Miri {
|
||||||
|
type Output = ();
|
||||||
|
const ONLY_HOSTS: bool = false;
|
||||||
|
|
||||||
|
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
|
||||||
|
run.path("src/tools/miri")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_run(run: RunConfig<'_>) {
|
||||||
|
run.builder.ensure(Miri {
|
||||||
|
stage: run.builder.top_stage,
|
||||||
|
host: run.build_triple(),
|
||||||
|
target: run.target,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(self, builder: &Builder<'_>) {
|
||||||
|
let stage = self.stage;
|
||||||
|
let host = self.host;
|
||||||
|
let target = self.target;
|
||||||
|
let compiler = builder.compiler(stage, host);
|
||||||
|
|
||||||
|
let miri = builder
|
||||||
|
.ensure(tool::Miri { compiler, target: self.host, extra_features: Vec::new() })
|
||||||
|
.expect("in-tree tool");
|
||||||
|
let miri_sysroot = test::Miri::build_miri_sysroot(builder, compiler, &miri, target);
|
||||||
|
|
||||||
|
// # Run miri.
|
||||||
|
// Running it via `cargo run` as that figures out the right dylib path.
|
||||||
|
// add_rustc_lib_path does not add the path that contains librustc_driver-<...>.so.
|
||||||
|
let mut miri = tool::prepare_tool_cargo(
|
||||||
|
builder,
|
||||||
|
compiler,
|
||||||
|
Mode::ToolRustc,
|
||||||
|
host,
|
||||||
|
"run",
|
||||||
|
"src/tools/miri",
|
||||||
|
SourceType::InTree,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
miri.add_rustc_lib_path(builder, compiler);
|
||||||
|
// Forward arguments.
|
||||||
|
miri.arg("--").arg("--target").arg(target.rustc_target_arg());
|
||||||
|
miri.args(builder.config.cmd.args());
|
||||||
|
|
||||||
|
// miri tests need to know about the stage sysroot
|
||||||
|
miri.env("MIRI_SYSROOT", &miri_sysroot);
|
||||||
|
|
||||||
|
let mut miri = Command::from(miri);
|
||||||
|
builder.run(&mut miri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -465,6 +465,66 @@ pub struct Miri {
|
||||||
target: TargetSelection,
|
target: TargetSelection,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Miri {
|
||||||
|
/// Run `cargo miri setup` for the given target, return where the Miri sysroot was put.
|
||||||
|
pub fn build_miri_sysroot(
|
||||||
|
builder: &Builder<'_>,
|
||||||
|
compiler: Compiler,
|
||||||
|
miri: &Path,
|
||||||
|
target: TargetSelection,
|
||||||
|
) -> String {
|
||||||
|
let miri_sysroot = builder.out.join(compiler.host.triple).join("miri-sysrot");
|
||||||
|
let mut cargo = tool::prepare_tool_cargo(
|
||||||
|
builder,
|
||||||
|
compiler,
|
||||||
|
Mode::ToolRustc,
|
||||||
|
compiler.host,
|
||||||
|
"run",
|
||||||
|
"src/tools/miri/cargo-miri",
|
||||||
|
SourceType::InTree,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
cargo.add_rustc_lib_path(builder, compiler);
|
||||||
|
cargo.arg("--").arg("miri").arg("setup");
|
||||||
|
cargo.arg("--target").arg(target.rustc_target_arg());
|
||||||
|
|
||||||
|
// Tell `cargo miri setup` where to find the sources.
|
||||||
|
cargo.env("MIRI_LIB_SRC", builder.src.join("library"));
|
||||||
|
// Tell it where to find Miri.
|
||||||
|
cargo.env("MIRI", &miri);
|
||||||
|
// Tell it where to put the sysroot.
|
||||||
|
cargo.env("MIRI_SYSROOT", &miri_sysroot);
|
||||||
|
// Debug things.
|
||||||
|
cargo.env("RUST_BACKTRACE", "1");
|
||||||
|
|
||||||
|
let mut cargo = Command::from(cargo);
|
||||||
|
builder.run(&mut cargo);
|
||||||
|
|
||||||
|
// # Determine where Miri put its sysroot.
|
||||||
|
// To this end, we run `cargo miri setup --print-sysroot` and capture the output.
|
||||||
|
// (We do this separately from the above so that when the setup actually
|
||||||
|
// happens we get some output.)
|
||||||
|
// We re-use the `cargo` from above.
|
||||||
|
cargo.arg("--print-sysroot");
|
||||||
|
|
||||||
|
// FIXME: Is there a way in which we can re-use the usual `run` helpers?
|
||||||
|
if builder.config.dry_run {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
builder.verbose(&format!("running: {:?}", cargo));
|
||||||
|
let out =
|
||||||
|
cargo.output().expect("We already ran `cargo miri setup` before and that worked");
|
||||||
|
assert!(out.status.success(), "`cargo miri setup` returned with non-0 exit code");
|
||||||
|
// Output is "<sysroot>\n".
|
||||||
|
let stdout = String::from_utf8(out.stdout)
|
||||||
|
.expect("`cargo miri setup` stdout is not valid UTF-8");
|
||||||
|
let sysroot = stdout.trim_end();
|
||||||
|
builder.verbose(&format!("`cargo miri setup --print-sysroot` said: {:?}", sysroot));
|
||||||
|
sysroot.to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Step for Miri {
|
impl Step for Miri {
|
||||||
type Output = ();
|
type Output = ();
|
||||||
const ONLY_HOSTS: bool = false;
|
const ONLY_HOSTS: bool = false;
|
||||||
|
@ -501,54 +561,8 @@ impl Step for Miri {
|
||||||
// sysroot does not seem to populate it, so we do that first.
|
// sysroot does not seem to populate it, so we do that first.
|
||||||
builder.ensure(compile::Std::new(compiler_std, host));
|
builder.ensure(compile::Std::new(compiler_std, host));
|
||||||
let sysroot = builder.sysroot(compiler_std);
|
let sysroot = builder.sysroot(compiler_std);
|
||||||
|
// We also need a Miri sysroot.
|
||||||
// # Run `cargo miri setup` for the given target.
|
let miri_sysroot = Miri::build_miri_sysroot(builder, compiler, &miri, target);
|
||||||
let mut cargo = tool::prepare_tool_cargo(
|
|
||||||
builder,
|
|
||||||
compiler,
|
|
||||||
Mode::ToolRustc,
|
|
||||||
host,
|
|
||||||
"run",
|
|
||||||
"src/tools/miri/cargo-miri",
|
|
||||||
SourceType::InTree,
|
|
||||||
&[],
|
|
||||||
);
|
|
||||||
cargo.add_rustc_lib_path(builder, compiler);
|
|
||||||
cargo.arg("--").arg("miri").arg("setup");
|
|
||||||
cargo.arg("--target").arg(target.rustc_target_arg());
|
|
||||||
|
|
||||||
// Tell `cargo miri setup` where to find the sources.
|
|
||||||
cargo.env("MIRI_LIB_SRC", builder.src.join("library"));
|
|
||||||
// Tell it where to find Miri.
|
|
||||||
cargo.env("MIRI", &miri);
|
|
||||||
// Debug things.
|
|
||||||
cargo.env("RUST_BACKTRACE", "1");
|
|
||||||
|
|
||||||
let mut cargo = Command::from(cargo);
|
|
||||||
builder.run(&mut cargo);
|
|
||||||
|
|
||||||
// # Determine where Miri put its sysroot.
|
|
||||||
// To this end, we run `cargo miri setup --print-sysroot` and capture the output.
|
|
||||||
// (We do this separately from the above so that when the setup actually
|
|
||||||
// happens we get some output.)
|
|
||||||
// We re-use the `cargo` from above.
|
|
||||||
cargo.arg("--print-sysroot");
|
|
||||||
|
|
||||||
// FIXME: Is there a way in which we can re-use the usual `run` helpers?
|
|
||||||
let miri_sysroot = if builder.config.dry_run {
|
|
||||||
String::new()
|
|
||||||
} else {
|
|
||||||
builder.verbose(&format!("running: {:?}", cargo));
|
|
||||||
let out =
|
|
||||||
cargo.output().expect("We already ran `cargo miri setup` before and that worked");
|
|
||||||
assert!(out.status.success(), "`cargo miri setup` returned with non-0 exit code");
|
|
||||||
// Output is "<sysroot>\n".
|
|
||||||
let stdout = String::from_utf8(out.stdout)
|
|
||||||
.expect("`cargo miri setup` stdout is not valid UTF-8");
|
|
||||||
let sysroot = stdout.trim_end();
|
|
||||||
builder.verbose(&format!("`cargo miri setup --print-sysroot` said: {:?}", sysroot));
|
|
||||||
sysroot.to_owned()
|
|
||||||
};
|
|
||||||
|
|
||||||
// # Run `cargo test`.
|
// # Run `cargo test`.
|
||||||
let mut cargo = tool::prepare_tool_cargo(
|
let mut cargo = tool::prepare_tool_cargo(
|
||||||
|
@ -566,7 +580,6 @@ impl Step for Miri {
|
||||||
// miri tests need to know about the stage sysroot
|
// miri tests need to know about the stage sysroot
|
||||||
cargo.env("MIRI_SYSROOT", &miri_sysroot);
|
cargo.env("MIRI_SYSROOT", &miri_sysroot);
|
||||||
cargo.env("MIRI_HOST_SYSROOT", sysroot);
|
cargo.env("MIRI_HOST_SYSROOT", sysroot);
|
||||||
cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(compiler));
|
|
||||||
cargo.env("MIRI", &miri);
|
cargo.env("MIRI", &miri);
|
||||||
// propagate --bless
|
// propagate --bless
|
||||||
if builder.config.cmd.bless() {
|
if builder.config.cmd.bless() {
|
||||||
|
@ -607,7 +620,6 @@ impl Step for Miri {
|
||||||
// Tell `cargo miri` where to find things.
|
// Tell `cargo miri` where to find things.
|
||||||
cargo.env("MIRI_SYSROOT", &miri_sysroot);
|
cargo.env("MIRI_SYSROOT", &miri_sysroot);
|
||||||
cargo.env("MIRI_HOST_SYSROOT", sysroot);
|
cargo.env("MIRI_HOST_SYSROOT", sysroot);
|
||||||
cargo.env("RUSTC_LIB_PATH", builder.rustc_libdir(compiler));
|
|
||||||
cargo.env("MIRI", &miri);
|
cargo.env("MIRI", &miri);
|
||||||
// Debug things.
|
// Debug things.
|
||||||
cargo.env("RUST_BACKTRACE", "1");
|
cargo.env("RUST_BACKTRACE", "1");
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
0.12.7
|
0.13.1
|
|
@ -830,6 +830,9 @@ table,
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
#crate-search:hover, #crate-search:focus {
|
||||||
|
border-color: var(--crate-search-hover-border);
|
||||||
|
}
|
||||||
/* cancel stylistic differences in padding in firefox
|
/* cancel stylistic differences in padding in firefox
|
||||||
for "appearance: none"-style (or equivalent) <select>s */
|
for "appearance: none"-style (or equivalent) <select>s */
|
||||||
@-moz-document url-prefix() {
|
@-moz-document url-prefix() {
|
||||||
|
@ -853,8 +856,13 @@ so that we can apply CSS-filters to change the arrow color in themes */
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: 20px;
|
background-size: 20px;
|
||||||
background-position: calc(100% - 2px) 56%;
|
background-position: calc(100% - 2px) 56%;
|
||||||
/* image is black color, themes should apply a "filter" property to change the color */
|
/* image is black color */
|
||||||
background-image: url("down-arrow-927217e04c7463ac.svg");
|
background-image: url("down-arrow-927217e04c7463ac.svg");
|
||||||
|
/* changes the arrow image color */
|
||||||
|
filter: var(--crate-search-div-filter);
|
||||||
|
}
|
||||||
|
#crate-search-div:hover::after, #crate-search-div:focus-within::after {
|
||||||
|
filter: var(--crate-search-div-hover-filter);
|
||||||
}
|
}
|
||||||
#crate-search > option {
|
#crate-search > option {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
|
|
@ -67,6 +67,12 @@ Original by Dempfi (https://github.com/dempfi/ayu)
|
||||||
drop-shadow(0 1px 0 #fff)
|
drop-shadow(0 1px 0 #fff)
|
||||||
drop-shadow(-1px 0 0 #fff)
|
drop-shadow(-1px 0 0 #fff)
|
||||||
drop-shadow(0 -1px 0 #fff);
|
drop-shadow(0 -1px 0 #fff);
|
||||||
|
/* match border-color; uses https://codepen.io/sosuke/pen/Pjoqqp */
|
||||||
|
--crate-search-div-filter: invert(41%) sepia(12%) saturate(487%) hue-rotate(171deg)
|
||||||
|
brightness(94%) contrast(94%);
|
||||||
|
--crate-search-div-hover-filter: invert(98%) sepia(12%) saturate(81%) hue-rotate(343deg)
|
||||||
|
brightness(113%) contrast(76%);
|
||||||
|
--crate-search-hover-border: #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
|
@ -153,17 +159,6 @@ details.rustdoc-toggle > summary::before {
|
||||||
filter: invert(100%);
|
filter: invert(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#crate-search-div::after {
|
|
||||||
/* match border-color; uses https://codepen.io/sosuke/pen/Pjoqqp */
|
|
||||||
filter: invert(41%) sepia(12%) saturate(487%) hue-rotate(171deg) brightness(94%) contrast(94%);
|
|
||||||
}
|
|
||||||
#crate-search:hover, #crate-search:focus {
|
|
||||||
border-color: #e0e0e0 !important;
|
|
||||||
}
|
|
||||||
#crate-search-div:hover::after, #crate-search-div:focus-within::after {
|
|
||||||
filter: invert(98%) sepia(12%) saturate(81%) hue-rotate(343deg) brightness(113%) contrast(76%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-item .stab,
|
.module-item .stab,
|
||||||
.import-item .stab {
|
.import-item .stab {
|
||||||
color: #000;
|
color: #000;
|
||||||
|
|
|
@ -62,6 +62,12 @@
|
||||||
drop-shadow(0 1px 0 #fff)
|
drop-shadow(0 1px 0 #fff)
|
||||||
drop-shadow(-1px 0 0 #fff)
|
drop-shadow(-1px 0 0 #fff)
|
||||||
drop-shadow(0 -1px 0 #fff);
|
drop-shadow(0 -1px 0 #fff);
|
||||||
|
/* match border-color; uses https://codepen.io/sosuke/pen/Pjoqqp */
|
||||||
|
--crate-search-div-filter: invert(94%) sepia(0%) saturate(721%) hue-rotate(255deg)
|
||||||
|
brightness(90%) contrast(90%);
|
||||||
|
--crate-search-div-hover-filter: invert(69%) sepia(60%) saturate(6613%) hue-rotate(184deg)
|
||||||
|
brightness(100%) contrast(91%);
|
||||||
|
--crate-search-hover-border: #2196f3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
|
@ -84,17 +90,6 @@ details.rustdoc-toggle > summary::before {
|
||||||
filter: invert(100%);
|
filter: invert(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#crate-search-div::after {
|
|
||||||
/* match border-color; uses https://codepen.io/sosuke/pen/Pjoqqp */
|
|
||||||
filter: invert(94%) sepia(0%) saturate(721%) hue-rotate(255deg) brightness(90%) contrast(90%);
|
|
||||||
}
|
|
||||||
#crate-search:hover, #crate-search:focus {
|
|
||||||
border-color: #2196f3 !important;
|
|
||||||
}
|
|
||||||
#crate-search-div:hover::after, #crate-search-div:focus-within::after {
|
|
||||||
filter: invert(69%) sepia(60%) saturate(6613%) hue-rotate(184deg) brightness(100%) contrast(91%);
|
|
||||||
}
|
|
||||||
|
|
||||||
:target {
|
:target {
|
||||||
background-color: #494a3d;
|
background-color: #494a3d;
|
||||||
border-right: 3px solid #bb7410;
|
border-right: 3px solid #bb7410;
|
||||||
|
|
|
@ -59,6 +59,12 @@
|
||||||
--test-arrow-hover-color: #f5f5f5;
|
--test-arrow-hover-color: #f5f5f5;
|
||||||
--test-arrow-hover-background-color: #4e8bca;
|
--test-arrow-hover-background-color: #4e8bca;
|
||||||
--rust-logo-filter: initial;
|
--rust-logo-filter: initial;
|
||||||
|
/* match border-color; uses https://codepen.io/sosuke/pen/Pjoqqp */
|
||||||
|
--crate-search-div-filter: invert(100%) sepia(0%) saturate(4223%) hue-rotate(289deg)
|
||||||
|
brightness(114%) contrast(76%);
|
||||||
|
--crate-search-div-hover-filter: invert(44%) sepia(18%) saturate(23%) hue-rotate(317deg)
|
||||||
|
brightness(96%) contrast(93%);
|
||||||
|
--crate-search-hover-border: #717171;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
|
@ -77,17 +83,6 @@ body.source .example-wrap pre.rust a {
|
||||||
background: #eee;
|
background: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
#crate-search-div::after {
|
|
||||||
/* match border-color; uses https://codepen.io/sosuke/pen/Pjoqqp */
|
|
||||||
filter: invert(100%) sepia(0%) saturate(4223%) hue-rotate(289deg) brightness(114%) contrast(76%);
|
|
||||||
}
|
|
||||||
#crate-search:hover, #crate-search:focus {
|
|
||||||
border-color: #717171 !important;
|
|
||||||
}
|
|
||||||
#crate-search-div:hover::after, #crate-search-div:focus-within::after {
|
|
||||||
filter: invert(44%) sepia(18%) saturate(23%) hue-rotate(317deg) brightness(96%) contrast(93%);
|
|
||||||
}
|
|
||||||
|
|
||||||
:target {
|
:target {
|
||||||
background: #FDFFD3;
|
background: #FDFFD3;
|
||||||
border-right: 3px solid #AD7C37;
|
border-right: 3px solid #AD7C37;
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
// This test ensures that items and documentation code blocks are wrapped in <pre><code>
|
// This test ensures that items and documentation code blocks are wrapped in <pre><code>
|
||||||
|
|
||||||
|
// We need to disable this check because `implementors/test_docs/trait.AnotherOne.js`
|
||||||
|
// doesn't exist.
|
||||||
|
fail-on-request-error: false
|
||||||
goto: "file://" + |DOC_PATH| + "/test_docs/fn.foo.html"
|
goto: "file://" + |DOC_PATH| + "/test_docs/fn.foo.html"
|
||||||
size: (1080, 600)
|
size: (1080, 600)
|
||||||
// There should be four doc codeblocks.
|
// There should be four doc codeblocks.
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
// This test ensures that the color of the items in the type decl are working as expected.
|
// This test ensures that the color of the items in the type decl are working as expected.
|
||||||
|
|
||||||
|
// We need to disable this check because `implementors/test_docs/trait.TraitWithoutGenerics.js`
|
||||||
|
// doesn't exist.
|
||||||
|
fail-on-request-error: false
|
||||||
|
|
||||||
define-function: (
|
define-function: (
|
||||||
"check-colors",
|
"check-colors",
|
||||||
(
|
(
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
// This test checks that there are margins applied to methods with no docblocks.
|
// This test checks that there are margins applied to methods with no docblocks.
|
||||||
|
|
||||||
|
// We need to disable this check because `implementors/test_docs/trait.TraitWithNoDocblock.js`
|
||||||
|
// doesn't exist.
|
||||||
|
fail-on-request-error: false
|
||||||
|
|
||||||
goto: "file://" + |DOC_PATH| + "/test_docs/trait.TraitWithNoDocblocks.html"
|
goto: "file://" + |DOC_PATH| + "/test_docs/trait.TraitWithNoDocblocks.html"
|
||||||
// Check that the two methods are more than 24px apart.
|
// Check that the two methods are more than 24px apart.
|
||||||
compare-elements-position-near-false: ("//*[@id='tymethod.first_fn']", "//*[@id='tymethod.second_fn']", {"y": 24})
|
compare-elements-position-near-false: ("//*[@id='tymethod.first_fn']", "//*[@id='tymethod.second_fn']", {"y": 24})
|
||||||
|
|
|
@ -35,3 +35,43 @@ assert-css: ("#crate-search", {"width": "527px"})
|
||||||
assert-css: (".search-results-title", {"height": "44px", "width": "640px"})
|
assert-css: (".search-results-title", {"height": "44px", "width": "640px"})
|
||||||
// And we check that the `<select>` isn't bigger than its container (".search-results-title").
|
// And we check that the `<select>` isn't bigger than its container (".search-results-title").
|
||||||
assert-css: ("#search", {"width": "640px"})
|
assert-css: ("#search", {"width": "640px"})
|
||||||
|
|
||||||
|
// Now checking that the crate filter is working as expected too.
|
||||||
|
show-text: true
|
||||||
|
define-function: (
|
||||||
|
"check-filter",
|
||||||
|
(theme, border, filter, hover_border, hover_filter),
|
||||||
|
[
|
||||||
|
("local-storage", {"rustdoc-theme": |theme|, "rustdoc-use-system-theme": "false"}),
|
||||||
|
("reload"),
|
||||||
|
("wait-for", "#crate-search"),
|
||||||
|
("assert-css", ("#crate-search", {"border": "1px solid " + |border|})),
|
||||||
|
("assert-css", ("#crate-search-div::after", {"filter": |filter|})),
|
||||||
|
("move-cursor-to", "#crate-search"),
|
||||||
|
("assert-css", ("#crate-search", {"border": "1px solid " + |hover_border|})),
|
||||||
|
("assert-css", ("#crate-search-div::after", {"filter": |hover_filter|})),
|
||||||
|
("move-cursor-to", ".search-input"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
call-function: ("check-filter", {
|
||||||
|
"theme": "ayu",
|
||||||
|
"border": "rgb(92, 103, 115)",
|
||||||
|
"filter": "invert(0.41) sepia(0.12) saturate(4.87) hue-rotate(171deg) brightness(0.94) contrast(0.94)",
|
||||||
|
"hover_border": "rgb(224, 224, 224)",
|
||||||
|
"hover_filter": "invert(0.98) sepia(0.12) saturate(0.81) hue-rotate(343deg) brightness(1.13) contrast(0.76)",
|
||||||
|
})
|
||||||
|
call-function: ("check-filter", {
|
||||||
|
"theme": "dark",
|
||||||
|
"border": "rgb(224, 224, 224)",
|
||||||
|
"filter": "invert(0.94) sepia(0) saturate(7.21) hue-rotate(255deg) brightness(0.9) contrast(0.9)",
|
||||||
|
"hover_border": "rgb(33, 150, 243)",
|
||||||
|
"hover_filter": "invert(0.69) sepia(0.6) saturate(66.13) hue-rotate(184deg) brightness(1) contrast(0.91)",
|
||||||
|
})
|
||||||
|
call-function: ("check-filter", {
|
||||||
|
"theme": "light",
|
||||||
|
"border": "rgb(224, 224, 224)",
|
||||||
|
"filter": "invert(1) sepia(0) saturate(42.23) hue-rotate(289deg) brightness(1.14) contrast(0.76)",
|
||||||
|
"hover_border": "rgb(113, 113, 113)",
|
||||||
|
"hover_filter": "invert(0.44) sepia(0.18) saturate(0.23) hue-rotate(317deg) brightness(0.96) contrast(0.93)",
|
||||||
|
})
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
// Checks that the elements in the sidebar are alphabetically sorted.
|
// Checks that the elements in the sidebar are alphabetically sorted.
|
||||||
|
|
||||||
|
// We need to disable this check because `implementors/test_docs/trait.AnotherOne.js`
|
||||||
|
// doesn't exist.
|
||||||
|
fail-on-request-error: false
|
||||||
|
|
||||||
goto: "file://" + |DOC_PATH| + "/test_docs/trait.AnotherOne.html"
|
goto: "file://" + |DOC_PATH| + "/test_docs/trait.AnotherOne.html"
|
||||||
assert-text: (".sidebar-elems section .block li:nth-of-type(1) > a", "another")
|
assert-text: (".sidebar-elems section .block li:nth-of-type(1) > a", "another")
|
||||||
assert-text: (".sidebar-elems section .block li:nth-of-type(2) > a", "func1")
|
assert-text: (".sidebar-elems section .block li:nth-of-type(2) > a", "func1")
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
// This test ensures that the items declaration content overflow is handled inside the <pre> directly.
|
// This test ensures that the items declaration content overflow is handled inside the <pre> directly.
|
||||||
|
|
||||||
|
// We need to disable this check because
|
||||||
|
// `implementors/test_docs/trait.ALongNameBecauseItHelpsTestingTheCurrentProblem.js`
|
||||||
|
// doesn't exist.
|
||||||
|
fail-on-request-error: false
|
||||||
|
|
||||||
goto: "file://" + |DOC_PATH| + "/lib2/long_trait/trait.ALongNameBecauseItHelpsTestingTheCurrentProblem.html"
|
goto: "file://" + |DOC_PATH| + "/lib2/long_trait/trait.ALongNameBecauseItHelpsTestingTheCurrentProblem.html"
|
||||||
// We set a fixed size so there is no chance of "random" resize.
|
// We set a fixed size so there is no chance of "random" resize.
|
||||||
size: (1100, 800)
|
size: (1100, 800)
|
||||||
|
|
11
src/test/ui/parser/use-colon-as-mod-sep.rs
Normal file
11
src/test/ui/parser/use-colon-as-mod-sep.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// Recover from using a colon as a path separator.
|
||||||
|
|
||||||
|
use std::process:Command;
|
||||||
|
//~^ ERROR expected `::`, found `:`
|
||||||
|
use std:fs::File;
|
||||||
|
//~^ ERROR expected `::`, found `:`
|
||||||
|
use std:collections:HashMap;
|
||||||
|
//~^ ERROR expected `::`, found `:`
|
||||||
|
//~| ERROR expected `::`, found `:`
|
||||||
|
|
||||||
|
fn main() { }
|
28
src/test/ui/parser/use-colon-as-mod-sep.stderr
Normal file
28
src/test/ui/parser/use-colon-as-mod-sep.stderr
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
error: expected `::`, found `:`
|
||||||
|
--> $DIR/use-colon-as-mod-sep.rs:3:17
|
||||||
|
|
|
||||||
|
LL | use std::process:Command;
|
||||||
|
| ^ help: use double colon
|
||||||
|
|
|
||||||
|
= note: import paths are delimited using `::`
|
||||||
|
|
||||||
|
error: expected `::`, found `:`
|
||||||
|
--> $DIR/use-colon-as-mod-sep.rs:5:8
|
||||||
|
|
|
||||||
|
LL | use std:fs::File;
|
||||||
|
| ^ help: use double colon
|
||||||
|
|
||||||
|
error: expected `::`, found `:`
|
||||||
|
--> $DIR/use-colon-as-mod-sep.rs:7:8
|
||||||
|
|
|
||||||
|
LL | use std:collections:HashMap;
|
||||||
|
| ^ help: use double colon
|
||||||
|
|
||||||
|
error: expected `::`, found `:`
|
||||||
|
--> $DIR/use-colon-as-mod-sep.rs:7:20
|
||||||
|
|
|
||||||
|
LL | use std:collections:HashMap;
|
||||||
|
| ^ help: use double colon
|
||||||
|
|
||||||
|
error: aborting due to 4 previous errors
|
||||||
|
|
10
src/test/ui/type/issue-103271.rs
Normal file
10
src/test/ui/type/issue-103271.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
fn main() {
|
||||||
|
let iter_fun = <&[u32]>::iter;
|
||||||
|
//~^ ERROR no function or associated item named `iter` found for reference `&[u32]` in the current scope [E0599]
|
||||||
|
//~| function or associated item not found in `&[u32]`
|
||||||
|
//~| HELP the function `iter` is implemented on `[u32]`
|
||||||
|
for item in iter_fun(&[1,1]) {
|
||||||
|
let x: &u32 = item;
|
||||||
|
assert_eq!(x, &1);
|
||||||
|
}
|
||||||
|
}
|
14
src/test/ui/type/issue-103271.stderr
Normal file
14
src/test/ui/type/issue-103271.stderr
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
error[E0599]: no function or associated item named `iter` found for reference `&[u32]` in the current scope
|
||||||
|
--> $DIR/issue-103271.rs:2:30
|
||||||
|
|
|
||||||
|
LL | let iter_fun = <&[u32]>::iter;
|
||||||
|
| ^^^^ function or associated item not found in `&[u32]`
|
||||||
|
|
|
||||||
|
help: the function `iter` is implemented on `[u32]`
|
||||||
|
|
|
||||||
|
LL | let iter_fun = <[u32]>::iter;
|
||||||
|
| ~~~~~
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0599`.
|
|
@ -433,8 +433,10 @@ Moreover, Miri recognizes some environment variables:
|
||||||
trigger a re-build of the standard library; you have to clear the Miri build
|
trigger a re-build of the standard library; you have to clear the Miri build
|
||||||
cache manually (on Linux, `rm -rf ~/.cache/miri`).
|
cache manually (on Linux, `rm -rf ~/.cache/miri`).
|
||||||
* `MIRI_SYSROOT` (recognized by `cargo miri` and the Miri driver) indicates the sysroot to use. When
|
* `MIRI_SYSROOT` (recognized by `cargo miri` and the Miri driver) indicates the sysroot to use. When
|
||||||
using `cargo miri`, only set this if you do not want to use the automatically created sysroot. For
|
using `cargo miri`, this skips the automatic setup -- only set this if you do not want to use the
|
||||||
directly invoking the Miri driver, this variable (or a `--sysroot` flag) is mandatory.
|
automatically created sysroot. For directly invoking the Miri driver, this variable (or a
|
||||||
|
`--sysroot` flag) is mandatory. When invoking `cargo miri setup`, this indicates where the sysroot
|
||||||
|
will be put.
|
||||||
* `MIRI_TEST_TARGET` (recognized by the test suite and the `./miri` script) indicates which target
|
* `MIRI_TEST_TARGET` (recognized by the test suite and the `./miri` script) indicates which target
|
||||||
architecture to test against. `miri` and `cargo miri` accept the `--target` flag for the same
|
architecture to test against. `miri` and `cargo miri` accept the `--target` flag for the same
|
||||||
purpose.
|
purpose.
|
||||||
|
|
|
@ -17,10 +17,8 @@ pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta
|
||||||
let only_setup = matches!(subcommand, MiriCommand::Setup);
|
let only_setup = matches!(subcommand, MiriCommand::Setup);
|
||||||
let ask_user = !only_setup;
|
let ask_user = !only_setup;
|
||||||
let print_sysroot = only_setup && has_arg_flag("--print-sysroot"); // whether we just print the sysroot path
|
let print_sysroot = only_setup && has_arg_flag("--print-sysroot"); // whether we just print the sysroot path
|
||||||
if std::env::var_os("MIRI_SYSROOT").is_some() {
|
if !only_setup && std::env::var_os("MIRI_SYSROOT").is_some() {
|
||||||
if only_setup {
|
// Skip setup step if MIRI_SYSROOT is explicitly set, *unless* we are `cargo miri setup`.
|
||||||
println!("WARNING: MIRI_SYSROOT already set, not doing anything.")
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,8 +59,13 @@ pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine where to put the sysroot.
|
// Determine where to put the sysroot.
|
||||||
|
let sysroot_dir = match std::env::var_os("MIRI_SYSROOT") {
|
||||||
|
Some(dir) => PathBuf::from(dir),
|
||||||
|
None => {
|
||||||
let user_dirs = directories::ProjectDirs::from("org", "rust-lang", "miri").unwrap();
|
let user_dirs = directories::ProjectDirs::from("org", "rust-lang", "miri").unwrap();
|
||||||
let sysroot_dir = user_dirs.cache_dir();
|
user_dirs.cache_dir().to_owned()
|
||||||
|
}
|
||||||
|
};
|
||||||
// Sysroot configuration and build details.
|
// Sysroot configuration and build details.
|
||||||
let sysroot_config = if std::env::var_os("MIRI_NO_STD").is_some() {
|
let sysroot_config = if std::env::var_os("MIRI_NO_STD").is_some() {
|
||||||
SysrootConfig::NoStd
|
SysrootConfig::NoStd
|
||||||
|
@ -111,7 +114,7 @@ pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta
|
||||||
(command, rustflags)
|
(command, rustflags)
|
||||||
};
|
};
|
||||||
// Make sure all target-level Miri invocations know their sysroot.
|
// Make sure all target-level Miri invocations know their sysroot.
|
||||||
std::env::set_var("MIRI_SYSROOT", sysroot_dir);
|
std::env::set_var("MIRI_SYSROOT", &sysroot_dir);
|
||||||
|
|
||||||
// Do the build.
|
// Do the build.
|
||||||
if only_setup {
|
if only_setup {
|
||||||
|
@ -121,7 +124,7 @@ pub fn setup(subcommand: &MiriCommand, target: &str, rustc_version: &VersionMeta
|
||||||
// We want to be quiet, but still let the user know that something is happening.
|
// We want to be quiet, but still let the user know that something is happening.
|
||||||
eprint!("Preparing a sysroot for Miri (target: {target})... ");
|
eprint!("Preparing a sysroot for Miri (target: {target})... ");
|
||||||
}
|
}
|
||||||
Sysroot::new(sysroot_dir, target)
|
Sysroot::new(&sysroot_dir, target)
|
||||||
.build_from_source(&rust_src, BuildMode::Check, sysroot_config, rustc_version, cargo_cmd)
|
.build_from_source(&rust_src, BuildMode::Check, sysroot_config, rustc_version, cargo_cmd)
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
if only_setup {
|
if only_setup {
|
||||||
|
|
|
@ -13,8 +13,7 @@ Forum for questions: https://users.rust-lang.org/c/ide/14
|
||||||
|
|
||||||
Before submitting, please make sure that you're not running into one of these known issues:
|
Before submitting, please make sure that you're not running into one of these known issues:
|
||||||
|
|
||||||
1. extension doesn't load in VSCodium: #11080
|
1. on-the-fly diagnostics are mostly unimplemented (`cargo check` diagnostics will be shown when saving a file): #3107
|
||||||
2. on-the-fly diagnostics are mostly unimplemented (`cargo check` diagnostics will be shown when saving a file): #3107
|
|
||||||
|
|
||||||
Otherwise please try to provide information which will help us to fix the issue faster. Minimal reproducible examples with few dependencies are especially lovely <3.
|
Otherwise please try to provide information which will help us to fix the issue faster. Minimal reproducible examples with few dependencies are especially lovely <3.
|
||||||
-->
|
-->
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
name: Critical Nightly Regression
|
name: Critical Nightly Regression
|
||||||
about: You are using nightly rust-analyzer and the latest version is unusable.
|
about: You are using nightly rust-analyzer and the latest version is unusable.
|
||||||
title: ''
|
title: ''
|
||||||
labels: ''
|
labels: 'Broken Window'
|
||||||
assignees: 'matklad'
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -14,4 +14,3 @@ Please try to provide information which will help us to fix the issue faster. Mi
|
||||||
-->
|
-->
|
||||||
|
|
||||||
This is a serious regression in nightly and it's important to fix it before the next release.
|
This is a serious regression in nightly and it's important to fix it before the next release.
|
||||||
@matklad, please take a look.
|
|
||||||
|
|
|
@ -257,8 +257,7 @@ jobs:
|
||||||
- name: Publish Extension (OpenVSX, release)
|
- name: Publish Extension (OpenVSX, release)
|
||||||
if: github.ref == 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer')
|
if: github.ref == 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer')
|
||||||
working-directory: ./editors/code
|
working-directory: ./editors/code
|
||||||
# token from https://dev.azure.com/rust-analyzer/
|
run: npx ovsx publish --pat ${{ secrets.OPENVSX_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix
|
||||||
run: npx ovsx publish --pat ${{ secrets.OPENVSX_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix || true
|
|
||||||
timeout-minutes: 2
|
timeout-minutes: 2
|
||||||
|
|
||||||
- name: Publish Extension (Code Marketplace, nightly)
|
- name: Publish Extension (Code Marketplace, nightly)
|
||||||
|
@ -269,5 +268,5 @@ jobs:
|
||||||
- name: Publish Extension (OpenVSX, nightly)
|
- name: Publish Extension (OpenVSX, nightly)
|
||||||
if: github.ref != 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer')
|
if: github.ref != 'refs/heads/release' && (github.repository == 'rust-analyzer/rust-analyzer' || github.repository == 'rust-lang/rust-analyzer')
|
||||||
working-directory: ./editors/code
|
working-directory: ./editors/code
|
||||||
run: npx ovsx publish --pat ${{ secrets.OPENVSX_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix || true
|
run: npx ovsx publish --pat ${{ secrets.OPENVSX_TOKEN }} --packagePath ../../dist/rust-analyzer-*.vsix
|
||||||
timeout-minutes: 2
|
timeout-minutes: 2
|
||||||
|
|
|
@ -143,9 +143,12 @@ pub(crate) fn print_type_ref(type_ref: &TypeRef, buf: &mut dyn Write) -> fmt::Re
|
||||||
print_type_ref(elem, buf)?;
|
print_type_ref(elem, buf)?;
|
||||||
write!(buf, "]")?;
|
write!(buf, "]")?;
|
||||||
}
|
}
|
||||||
TypeRef::Fn(args_and_ret, varargs) => {
|
TypeRef::Fn(args_and_ret, varargs, is_unsafe) => {
|
||||||
let ((_, return_type), args) =
|
let ((_, return_type), args) =
|
||||||
args_and_ret.split_last().expect("TypeRef::Fn is missing return type");
|
args_and_ret.split_last().expect("TypeRef::Fn is missing return type");
|
||||||
|
if *is_unsafe {
|
||||||
|
write!(buf, "unsafe ")?;
|
||||||
|
}
|
||||||
write!(buf, "fn(")?;
|
write!(buf, "fn(")?;
|
||||||
for (i, (_, typeref)) in args.iter().enumerate() {
|
for (i, (_, typeref)) in args.iter().enumerate() {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
|
|
|
@ -119,7 +119,7 @@ pub enum TypeRef {
|
||||||
Array(Box<TypeRef>, ConstScalarOrPath),
|
Array(Box<TypeRef>, ConstScalarOrPath),
|
||||||
Slice(Box<TypeRef>),
|
Slice(Box<TypeRef>),
|
||||||
/// A fn pointer. Last element of the vector is the return type.
|
/// A fn pointer. Last element of the vector is the return type.
|
||||||
Fn(Vec<(Option<Name>, TypeRef)>, bool /*varargs*/),
|
Fn(Vec<(Option<Name>, TypeRef)>, bool /*varargs*/, bool /*is_unsafe*/),
|
||||||
ImplTrait(Vec<Interned<TypeBound>>),
|
ImplTrait(Vec<Interned<TypeBound>>),
|
||||||
DynTrait(Vec<Interned<TypeBound>>),
|
DynTrait(Vec<Interned<TypeBound>>),
|
||||||
Macro(AstId<ast::MacroCall>),
|
Macro(AstId<ast::MacroCall>),
|
||||||
|
@ -229,7 +229,7 @@ impl TypeRef {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
params.push((None, ret_ty));
|
params.push((None, ret_ty));
|
||||||
TypeRef::Fn(params, is_varargs)
|
TypeRef::Fn(params, is_varargs, inner.unsafe_token().is_some())
|
||||||
}
|
}
|
||||||
// for types are close enough for our purposes to the inner type for now...
|
// for types are close enough for our purposes to the inner type for now...
|
||||||
ast::Type::ForType(inner) => TypeRef::from_ast_opt(ctx, inner.ty()),
|
ast::Type::ForType(inner) => TypeRef::from_ast_opt(ctx, inner.ty()),
|
||||||
|
@ -263,7 +263,7 @@ impl TypeRef {
|
||||||
fn go(type_ref: &TypeRef, f: &mut impl FnMut(&TypeRef)) {
|
fn go(type_ref: &TypeRef, f: &mut impl FnMut(&TypeRef)) {
|
||||||
f(type_ref);
|
f(type_ref);
|
||||||
match type_ref {
|
match type_ref {
|
||||||
TypeRef::Fn(params, _) => {
|
TypeRef::Fn(params, _, _) => {
|
||||||
params.iter().for_each(|(_, param_type)| go(param_type, f))
|
params.iter().for_each(|(_, param_type)| go(param_type, f))
|
||||||
}
|
}
|
||||||
TypeRef::Tuple(types) => types.iter().for_each(|t| go(t, f)),
|
TypeRef::Tuple(types) => types.iter().for_each(|t| go(t, f)),
|
||||||
|
|
|
@ -1187,8 +1187,11 @@ impl HirDisplay for TypeRef {
|
||||||
inner.hir_fmt(f)?;
|
inner.hir_fmt(f)?;
|
||||||
write!(f, "]")?;
|
write!(f, "]")?;
|
||||||
}
|
}
|
||||||
TypeRef::Fn(parameters, is_varargs) => {
|
&TypeRef::Fn(ref parameters, is_varargs, is_unsafe) => {
|
||||||
// FIXME: Function pointer qualifiers.
|
// FIXME: Function pointer qualifiers.
|
||||||
|
if is_unsafe {
|
||||||
|
write!(f, "unsafe ")?;
|
||||||
|
}
|
||||||
write!(f, "fn(")?;
|
write!(f, "fn(")?;
|
||||||
if let Some(((_, return_type), function_parameters)) = parameters.split_last() {
|
if let Some(((_, return_type), function_parameters)) = parameters.split_last() {
|
||||||
for index in 0..function_parameters.len() {
|
for index in 0..function_parameters.len() {
|
||||||
|
@ -1203,7 +1206,7 @@ impl HirDisplay for TypeRef {
|
||||||
write!(f, ", ")?;
|
write!(f, ", ")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if *is_varargs {
|
if is_varargs {
|
||||||
write!(f, "{}...", if parameters.len() == 1 { "" } else { ", " })?;
|
write!(f, "{}...", if parameters.len() == 1 { "" } else { ", " })?;
|
||||||
}
|
}
|
||||||
write!(f, ")")?;
|
write!(f, ")")?;
|
||||||
|
|
|
@ -85,6 +85,7 @@ impl<'a> InferenceContext<'a> {
|
||||||
let ty = match &self.body[tgt_expr] {
|
let ty = match &self.body[tgt_expr] {
|
||||||
Expr::Missing => self.err_ty(),
|
Expr::Missing => self.err_ty(),
|
||||||
&Expr::If { condition, then_branch, else_branch } => {
|
&Expr::If { condition, then_branch, else_branch } => {
|
||||||
|
let expected = &expected.adjust_for_branches(&mut self.table);
|
||||||
self.infer_expr(
|
self.infer_expr(
|
||||||
condition,
|
condition,
|
||||||
&Expectation::has_type(TyKind::Scalar(Scalar::Bool).intern(Interner)),
|
&Expectation::has_type(TyKind::Scalar(Scalar::Bool).intern(Interner)),
|
||||||
|
|
|
@ -38,10 +38,12 @@ use std::sync::Arc;
|
||||||
use chalk_ir::{
|
use chalk_ir::{
|
||||||
fold::{Shift, TypeFoldable},
|
fold::{Shift, TypeFoldable},
|
||||||
interner::HasInterner,
|
interner::HasInterner,
|
||||||
NoSolution,
|
NoSolution, UniverseIndex,
|
||||||
};
|
};
|
||||||
use hir_def::{expr::ExprId, type_ref::Rawness, TypeOrConstParamId};
|
use hir_def::{expr::ExprId, type_ref::Rawness, TypeOrConstParamId};
|
||||||
|
use hir_expand::name;
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
|
use traits::FnTrait;
|
||||||
use utils::Generics;
|
use utils::Generics;
|
||||||
|
|
||||||
use crate::{consteval::unknown_const, db::HirDatabase, utils::generics};
|
use crate::{consteval::unknown_const, db::HirDatabase, utils::generics};
|
||||||
|
@ -208,6 +210,7 @@ pub(crate) fn make_binders<T: HasInterner<Interner = Interner>>(
|
||||||
pub struct CallableSig {
|
pub struct CallableSig {
|
||||||
params_and_return: Arc<[Ty]>,
|
params_and_return: Arc<[Ty]>,
|
||||||
is_varargs: bool,
|
is_varargs: bool,
|
||||||
|
safety: Safety,
|
||||||
}
|
}
|
||||||
|
|
||||||
has_interner!(CallableSig);
|
has_interner!(CallableSig);
|
||||||
|
@ -216,9 +219,14 @@ has_interner!(CallableSig);
|
||||||
pub type PolyFnSig = Binders<CallableSig>;
|
pub type PolyFnSig = Binders<CallableSig>;
|
||||||
|
|
||||||
impl CallableSig {
|
impl CallableSig {
|
||||||
pub fn from_params_and_return(mut params: Vec<Ty>, ret: Ty, is_varargs: bool) -> CallableSig {
|
pub fn from_params_and_return(
|
||||||
|
mut params: Vec<Ty>,
|
||||||
|
ret: Ty,
|
||||||
|
is_varargs: bool,
|
||||||
|
safety: Safety,
|
||||||
|
) -> CallableSig {
|
||||||
params.push(ret);
|
params.push(ret);
|
||||||
CallableSig { params_and_return: params.into(), is_varargs }
|
CallableSig { params_and_return: params.into(), is_varargs, safety }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_fn_ptr(fn_ptr: &FnPointer) -> CallableSig {
|
pub fn from_fn_ptr(fn_ptr: &FnPointer) -> CallableSig {
|
||||||
|
@ -235,13 +243,14 @@ impl CallableSig {
|
||||||
.map(|arg| arg.assert_ty_ref(Interner).clone())
|
.map(|arg| arg.assert_ty_ref(Interner).clone())
|
||||||
.collect(),
|
.collect(),
|
||||||
is_varargs: fn_ptr.sig.variadic,
|
is_varargs: fn_ptr.sig.variadic,
|
||||||
|
safety: fn_ptr.sig.safety,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_fn_ptr(&self) -> FnPointer {
|
pub fn to_fn_ptr(&self) -> FnPointer {
|
||||||
FnPointer {
|
FnPointer {
|
||||||
num_binders: 0,
|
num_binders: 0,
|
||||||
sig: FnSig { abi: (), safety: Safety::Safe, variadic: self.is_varargs },
|
sig: FnSig { abi: (), safety: self.safety, variadic: self.is_varargs },
|
||||||
substitution: FnSubst(Substitution::from_iter(
|
substitution: FnSubst(Substitution::from_iter(
|
||||||
Interner,
|
Interner,
|
||||||
self.params_and_return.iter().cloned(),
|
self.params_and_return.iter().cloned(),
|
||||||
|
@ -266,7 +275,11 @@ impl TypeFoldable<Interner> for CallableSig {
|
||||||
) -> Result<Self, E> {
|
) -> Result<Self, E> {
|
||||||
let vec = self.params_and_return.to_vec();
|
let vec = self.params_and_return.to_vec();
|
||||||
let folded = vec.try_fold_with(folder, outer_binder)?;
|
let folded = vec.try_fold_with(folder, outer_binder)?;
|
||||||
Ok(CallableSig { params_and_return: folded.into(), is_varargs: self.is_varargs })
|
Ok(CallableSig {
|
||||||
|
params_and_return: folded.into(),
|
||||||
|
is_varargs: self.is_varargs,
|
||||||
|
safety: self.safety,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,3 +521,68 @@ where
|
||||||
});
|
});
|
||||||
Canonical { value, binders: chalk_ir::CanonicalVarKinds::from_iter(Interner, kinds) }
|
Canonical { value, binders: chalk_ir::CanonicalVarKinds::from_iter(Interner, kinds) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn callable_sig_from_fnonce(
|
||||||
|
self_ty: &Canonical<Ty>,
|
||||||
|
env: Arc<TraitEnvironment>,
|
||||||
|
db: &dyn HirDatabase,
|
||||||
|
) -> Option<CallableSig> {
|
||||||
|
let krate = env.krate;
|
||||||
|
let fn_once_trait = FnTrait::FnOnce.get_id(db, krate)?;
|
||||||
|
let output_assoc_type = db.trait_data(fn_once_trait).associated_type_by_name(&name![Output])?;
|
||||||
|
|
||||||
|
let mut kinds = self_ty.binders.interned().to_vec();
|
||||||
|
let b = TyBuilder::trait_ref(db, fn_once_trait);
|
||||||
|
if b.remaining() != 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let fn_once = b
|
||||||
|
.push(self_ty.value.clone())
|
||||||
|
.fill_with_bound_vars(DebruijnIndex::INNERMOST, kinds.len())
|
||||||
|
.build();
|
||||||
|
kinds.extend(fn_once.substitution.iter(Interner).skip(1).map(|x| {
|
||||||
|
let vk = match x.data(Interner) {
|
||||||
|
chalk_ir::GenericArgData::Ty(_) => {
|
||||||
|
chalk_ir::VariableKind::Ty(chalk_ir::TyVariableKind::General)
|
||||||
|
}
|
||||||
|
chalk_ir::GenericArgData::Lifetime(_) => chalk_ir::VariableKind::Lifetime,
|
||||||
|
chalk_ir::GenericArgData::Const(c) => {
|
||||||
|
chalk_ir::VariableKind::Const(c.data(Interner).ty.clone())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
chalk_ir::WithKind::new(vk, UniverseIndex::ROOT)
|
||||||
|
}));
|
||||||
|
|
||||||
|
// FIXME: chalk refuses to solve `<Self as FnOnce<^0.0>>::Output == ^0.1`, so we first solve
|
||||||
|
// `<Self as FnOnce<^0.0>>` and then replace `^0.0` with the concrete argument tuple.
|
||||||
|
let trait_env = env.env.clone();
|
||||||
|
let obligation = InEnvironment { goal: fn_once.cast(Interner), environment: trait_env };
|
||||||
|
let canonical =
|
||||||
|
Canonical { binders: CanonicalVarKinds::from_iter(Interner, kinds), value: obligation };
|
||||||
|
let subst = match db.trait_solve(krate, canonical) {
|
||||||
|
Some(Solution::Unique(vars)) => vars.value.subst,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
let args = subst.at(Interner, self_ty.binders.interned().len()).ty(Interner)?;
|
||||||
|
let params = match args.kind(Interner) {
|
||||||
|
chalk_ir::TyKind::Tuple(_, subst) => {
|
||||||
|
subst.iter(Interner).filter_map(|arg| arg.ty(Interner).cloned()).collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
if params.iter().any(|ty| ty.is_unknown()) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fn_once = TyBuilder::trait_ref(db, fn_once_trait)
|
||||||
|
.push(self_ty.value.clone())
|
||||||
|
.push(args.clone())
|
||||||
|
.build();
|
||||||
|
let projection =
|
||||||
|
TyBuilder::assoc_type_projection(db, output_assoc_type, Some(fn_once.substitution.clone()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let ret_ty = db.normalize_projection(projection, env);
|
||||||
|
|
||||||
|
Some(CallableSig::from_params_and_return(params, ret_ty.clone(), false, Safety::Safe))
|
||||||
|
}
|
||||||
|
|
|
@ -227,13 +227,17 @@ impl<'a> TyLoweringContext<'a> {
|
||||||
.intern(Interner)
|
.intern(Interner)
|
||||||
}
|
}
|
||||||
TypeRef::Placeholder => TyKind::Error.intern(Interner),
|
TypeRef::Placeholder => TyKind::Error.intern(Interner),
|
||||||
TypeRef::Fn(params, is_varargs) => {
|
&TypeRef::Fn(ref params, variadic, is_unsafe) => {
|
||||||
let substs = self.with_shifted_in(DebruijnIndex::ONE, |ctx| {
|
let substs = self.with_shifted_in(DebruijnIndex::ONE, |ctx| {
|
||||||
Substitution::from_iter(Interner, params.iter().map(|(_, tr)| ctx.lower_ty(tr)))
|
Substitution::from_iter(Interner, params.iter().map(|(_, tr)| ctx.lower_ty(tr)))
|
||||||
});
|
});
|
||||||
TyKind::Function(FnPointer {
|
TyKind::Function(FnPointer {
|
||||||
num_binders: 0, // FIXME lower `for<'a> fn()` correctly
|
num_binders: 0, // FIXME lower `for<'a> fn()` correctly
|
||||||
sig: FnSig { abi: (), safety: Safety::Safe, variadic: *is_varargs },
|
sig: FnSig {
|
||||||
|
abi: (),
|
||||||
|
safety: if is_unsafe { Safety::Unsafe } else { Safety::Safe },
|
||||||
|
variadic,
|
||||||
|
},
|
||||||
substitution: FnSubst(substs),
|
substitution: FnSubst(substs),
|
||||||
})
|
})
|
||||||
.intern(Interner)
|
.intern(Interner)
|
||||||
|
@ -1573,7 +1577,12 @@ fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig {
|
||||||
.with_type_param_mode(ParamLoweringMode::Variable);
|
.with_type_param_mode(ParamLoweringMode::Variable);
|
||||||
let ret = ctx_ret.lower_ty(&data.ret_type);
|
let ret = ctx_ret.lower_ty(&data.ret_type);
|
||||||
let generics = generics(db.upcast(), def.into());
|
let generics = generics(db.upcast(), def.into());
|
||||||
let sig = CallableSig::from_params_and_return(params, ret, data.is_varargs());
|
let sig = CallableSig::from_params_and_return(
|
||||||
|
params,
|
||||||
|
ret,
|
||||||
|
data.is_varargs(),
|
||||||
|
if data.has_unsafe_kw() { Safety::Unsafe } else { Safety::Safe },
|
||||||
|
);
|
||||||
make_binders(db, &generics, sig)
|
make_binders(db, &generics, sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1617,7 +1626,7 @@ fn fn_sig_for_struct_constructor(db: &dyn HirDatabase, def: StructId) -> PolyFnS
|
||||||
TyLoweringContext::new(db, &resolver).with_type_param_mode(ParamLoweringMode::Variable);
|
TyLoweringContext::new(db, &resolver).with_type_param_mode(ParamLoweringMode::Variable);
|
||||||
let params = fields.iter().map(|(_, field)| ctx.lower_ty(&field.type_ref)).collect::<Vec<_>>();
|
let params = fields.iter().map(|(_, field)| ctx.lower_ty(&field.type_ref)).collect::<Vec<_>>();
|
||||||
let (ret, binders) = type_for_adt(db, def.into()).into_value_and_skipped_binders();
|
let (ret, binders) = type_for_adt(db, def.into()).into_value_and_skipped_binders();
|
||||||
Binders::new(binders, CallableSig::from_params_and_return(params, ret, false))
|
Binders::new(binders, CallableSig::from_params_and_return(params, ret, false, Safety::Safe))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the type of a tuple struct constructor.
|
/// Build the type of a tuple struct constructor.
|
||||||
|
@ -1644,7 +1653,7 @@ fn fn_sig_for_enum_variant_constructor(db: &dyn HirDatabase, def: EnumVariantId)
|
||||||
TyLoweringContext::new(db, &resolver).with_type_param_mode(ParamLoweringMode::Variable);
|
TyLoweringContext::new(db, &resolver).with_type_param_mode(ParamLoweringMode::Variable);
|
||||||
let params = fields.iter().map(|(_, field)| ctx.lower_ty(&field.type_ref)).collect::<Vec<_>>();
|
let params = fields.iter().map(|(_, field)| ctx.lower_ty(&field.type_ref)).collect::<Vec<_>>();
|
||||||
let (ret, binders) = type_for_adt(db, def.parent.into()).into_value_and_skipped_binders();
|
let (ret, binders) = type_for_adt(db, def.parent.into()).into_value_and_skipped_binders();
|
||||||
Binders::new(binders, CallableSig::from_params_and_return(params, ret, false))
|
Binders::new(binders, CallableSig::from_params_and_return(params, ret, false, Safety::Safe))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the type of a tuple enum variant constructor.
|
/// Build the type of a tuple enum variant constructor.
|
||||||
|
|
|
@ -122,6 +122,23 @@ fn test() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn if_else_adjust_for_branches_discard_type_var() {
|
||||||
|
check_no_mismatches(
|
||||||
|
r#"
|
||||||
|
fn test() {
|
||||||
|
let f = || {
|
||||||
|
if true {
|
||||||
|
&""
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn match_first_coerce() {
|
fn match_first_coerce() {
|
||||||
check_no_mismatches(
|
check_no_mismatches(
|
||||||
|
@ -182,6 +199,22 @@ fn test() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn match_adjust_for_branches_discard_type_var() {
|
||||||
|
check_no_mismatches(
|
||||||
|
r#"
|
||||||
|
fn test() {
|
||||||
|
let f = || {
|
||||||
|
match 0i32 {
|
||||||
|
0i32 => &"",
|
||||||
|
_ => "",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn return_coerce_unknown() {
|
fn return_coerce_unknown() {
|
||||||
check_types(
|
check_types(
|
||||||
|
@ -357,7 +390,7 @@ fn test() {
|
||||||
let f: fn(u32) -> isize = foo;
|
let f: fn(u32) -> isize = foo;
|
||||||
// ^^^ adjustments: Pointer(ReifyFnPointer)
|
// ^^^ adjustments: Pointer(ReifyFnPointer)
|
||||||
let f: unsafe fn(u32) -> isize = foo;
|
let f: unsafe fn(u32) -> isize = foo;
|
||||||
// ^^^ adjustments: Pointer(ReifyFnPointer)
|
// ^^^ adjustments: Pointer(ReifyFnPointer), Pointer(UnsafeFnPointer)
|
||||||
}",
|
}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -388,7 +421,10 @@ fn coerce_closure_to_fn_ptr() {
|
||||||
check_no_mismatches(
|
check_no_mismatches(
|
||||||
r"
|
r"
|
||||||
fn test() {
|
fn test() {
|
||||||
let f: fn(u32) -> isize = |x| { 1 };
|
let f: fn(u32) -> u32 = |x| x;
|
||||||
|
// ^^^^^ adjustments: Pointer(ClosureFnPointer(Safe))
|
||||||
|
let f: unsafe fn(u32) -> u32 = |x| x;
|
||||||
|
// ^^^^^ adjustments: Pointer(ClosureFnPointer(Unsafe))
|
||||||
}",
|
}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2995,7 +2995,17 @@ impl Type {
|
||||||
let callee = match self.ty.kind(Interner) {
|
let callee = match self.ty.kind(Interner) {
|
||||||
TyKind::Closure(id, _) => Callee::Closure(*id),
|
TyKind::Closure(id, _) => Callee::Closure(*id),
|
||||||
TyKind::Function(_) => Callee::FnPtr,
|
TyKind::Function(_) => Callee::FnPtr,
|
||||||
_ => Callee::Def(self.ty.callable_def(db)?),
|
TyKind::FnDef(..) => Callee::Def(self.ty.callable_def(db)?),
|
||||||
|
_ => {
|
||||||
|
let ty = hir_ty::replace_errors_with_variables(&self.ty);
|
||||||
|
let sig = hir_ty::callable_sig_from_fnonce(&ty, self.env.clone(), db)?;
|
||||||
|
return Some(Callable {
|
||||||
|
ty: self.clone(),
|
||||||
|
sig,
|
||||||
|
callee: Callee::Other,
|
||||||
|
is_bound_method: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let sig = self.ty.callable_sig(db)?;
|
let sig = self.ty.callable_sig(db)?;
|
||||||
|
@ -3464,6 +3474,7 @@ enum Callee {
|
||||||
Def(CallableDefId),
|
Def(CallableDefId),
|
||||||
Closure(ClosureId),
|
Closure(ClosureId),
|
||||||
FnPtr,
|
FnPtr,
|
||||||
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum CallableKind {
|
pub enum CallableKind {
|
||||||
|
@ -3472,6 +3483,8 @@ pub enum CallableKind {
|
||||||
TupleEnumVariant(Variant),
|
TupleEnumVariant(Variant),
|
||||||
Closure,
|
Closure,
|
||||||
FnPtr,
|
FnPtr,
|
||||||
|
/// Some other type that implements `FnOnce`.
|
||||||
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Callable {
|
impl Callable {
|
||||||
|
@ -3483,6 +3496,7 @@ impl Callable {
|
||||||
Def(CallableDefId::EnumVariantId(it)) => CallableKind::TupleEnumVariant(it.into()),
|
Def(CallableDefId::EnumVariantId(it)) => CallableKind::TupleEnumVariant(it.into()),
|
||||||
Closure(_) => CallableKind::Closure,
|
Closure(_) => CallableKind::Closure,
|
||||||
FnPtr => CallableKind::FnPtr,
|
FnPtr => CallableKind::FnPtr,
|
||||||
|
Other => CallableKind::Other,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn receiver_param(&self, db: &dyn HirDatabase) -> Option<ast::SelfParam> {
|
pub fn receiver_param(&self, db: &dyn HirDatabase) -> Option<ast::SelfParam> {
|
||||||
|
|
|
@ -14,4 +14,5 @@ pub struct AssistConfig {
|
||||||
pub allowed: Option<Vec<AssistKind>>,
|
pub allowed: Option<Vec<AssistKind>>,
|
||||||
pub insert_use: InsertUseConfig,
|
pub insert_use: InsertUseConfig,
|
||||||
pub prefer_no_std: bool,
|
pub prefer_no_std: bool,
|
||||||
|
pub assist_emit_must_use: bool,
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,14 +69,14 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
|
||||||
let inferred_type = ty.display_source_code(ctx.db(), module.into()).ok()?;
|
let inferred_type = ty.display_source_code(ctx.db(), module.into()).ok()?;
|
||||||
acc.add(
|
acc.add(
|
||||||
AssistId("add_explicit_type", AssistKind::RefactorRewrite),
|
AssistId("add_explicit_type", AssistKind::RefactorRewrite),
|
||||||
format!("Insert explicit type `{}`", inferred_type),
|
format!("Insert explicit type `{inferred_type}`"),
|
||||||
pat_range,
|
pat_range,
|
||||||
|builder| match ascribed_ty {
|
|builder| match ascribed_ty {
|
||||||
Some(ascribed_ty) => {
|
Some(ascribed_ty) => {
|
||||||
builder.replace(ascribed_ty.syntax().text_range(), inferred_type);
|
builder.replace(ascribed_ty.syntax().text_range(), inferred_type);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
builder.insert(pat_range.end(), format!(": {}", inferred_type));
|
builder.insert(pat_range.end(), format!(": {inferred_type}"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -35,16 +35,16 @@ pub(crate) fn add_return_type(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt
|
||||||
match builder_edit_pos {
|
match builder_edit_pos {
|
||||||
InsertOrReplace::Insert(insert_pos, needs_whitespace) => {
|
InsertOrReplace::Insert(insert_pos, needs_whitespace) => {
|
||||||
let preceeding_whitespace = if needs_whitespace { " " } else { "" };
|
let preceeding_whitespace = if needs_whitespace { " " } else { "" };
|
||||||
builder.insert(insert_pos, &format!("{}-> {} ", preceeding_whitespace, ty))
|
builder.insert(insert_pos, &format!("{preceeding_whitespace}-> {ty} "))
|
||||||
}
|
}
|
||||||
InsertOrReplace::Replace(text_range) => {
|
InsertOrReplace::Replace(text_range) => {
|
||||||
builder.replace(text_range, &format!("-> {}", ty))
|
builder.replace(text_range, &format!("-> {ty}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let FnType::Closure { wrap_expr: true } = fn_type {
|
if let FnType::Closure { wrap_expr: true } = fn_type {
|
||||||
cov_mark::hit!(wrap_closure_non_block_expr);
|
cov_mark::hit!(wrap_closure_non_block_expr);
|
||||||
// `|x| x` becomes `|x| -> T x` which is invalid, so wrap it in a block
|
// `|x| x` becomes `|x| -> T x` which is invalid, so wrap it in a block
|
||||||
builder.replace(tail_expr.syntax().text_range(), &format!("{{{}}}", tail_expr));
|
builder.replace(tail_expr.syntax().text_range(), &format!("{{{tail_expr}}}"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -93,12 +93,13 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
|
||||||
builder.trigger_signature_help();
|
builder.trigger_signature_help();
|
||||||
match ctx.config.snippet_cap {
|
match ctx.config.snippet_cap {
|
||||||
Some(cap) => {
|
Some(cap) => {
|
||||||
let snip = format!("::<{}>", get_snippet_fish_head(number_of_arguments));
|
let fish_head = get_snippet_fish_head(number_of_arguments);
|
||||||
|
let snip = format!("::<{fish_head}>");
|
||||||
builder.insert_snippet(cap, ident.text_range().end(), snip)
|
builder.insert_snippet(cap, ident.text_range().end(), snip)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let fish_head = std::iter::repeat("_").take(number_of_arguments).format(", ");
|
let fish_head = std::iter::repeat("_").take(number_of_arguments).format(", ");
|
||||||
let snip = format!("::<{}>", fish_head);
|
let snip = format!("::<{fish_head}>");
|
||||||
builder.insert(ident.text_range().end(), snip);
|
builder.insert(ident.text_range().end(), snip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,7 +110,7 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
|
||||||
/// This will create a snippet string with tabstops marked
|
/// This will create a snippet string with tabstops marked
|
||||||
fn get_snippet_fish_head(number_of_arguments: usize) -> String {
|
fn get_snippet_fish_head(number_of_arguments: usize) -> String {
|
||||||
let mut fish_head = (1..number_of_arguments)
|
let mut fish_head = (1..number_of_arguments)
|
||||||
.format_with("", |i, f| f(&format_args!("${{{}:_}}, ", i)))
|
.format_with("", |i, f| f(&format_args!("${{{i}:_}}, ")))
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
// tabstop 0 is a special case and always the last one
|
// tabstop 0 is a special case and always the last one
|
||||||
|
|
|
@ -123,20 +123,20 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
|
||||||
let lhs_range = lhs.syntax().text_range();
|
let lhs_range = lhs.syntax().text_range();
|
||||||
let not_lhs = invert_boolean_expression(lhs);
|
let not_lhs = invert_boolean_expression(lhs);
|
||||||
|
|
||||||
edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
|
edit.replace(lhs_range, format!("!({not_lhs}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(rhs) = terms.pop_back() {
|
if let Some(rhs) = terms.pop_back() {
|
||||||
let rhs_range = rhs.syntax().text_range();
|
let rhs_range = rhs.syntax().text_range();
|
||||||
let not_rhs = invert_boolean_expression(rhs);
|
let not_rhs = invert_boolean_expression(rhs);
|
||||||
|
|
||||||
edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
|
edit.replace(rhs_range, format!("{not_rhs})"));
|
||||||
}
|
}
|
||||||
|
|
||||||
for term in terms {
|
for term in terms {
|
||||||
let term_range = term.syntax().text_range();
|
let term_range = term.syntax().text_range();
|
||||||
let not_term = invert_boolean_expression(term);
|
let not_term = invert_boolean_expression(term);
|
||||||
edit.replace(term_range, not_term.syntax().text());
|
edit.replace(term_range, not_term.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -127,10 +127,12 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
|
||||||
.sort_by_key(|import| Reverse(relevance_score(ctx, import, current_module.as_ref())));
|
.sort_by_key(|import| Reverse(relevance_score(ctx, import, current_module.as_ref())));
|
||||||
|
|
||||||
for import in proposed_imports {
|
for import in proposed_imports {
|
||||||
|
let import_path = import.import_path;
|
||||||
|
|
||||||
acc.add_group(
|
acc.add_group(
|
||||||
&group_label,
|
&group_label,
|
||||||
AssistId("auto_import", AssistKind::QuickFix),
|
AssistId("auto_import", AssistKind::QuickFix),
|
||||||
format!("Import `{}`", import.import_path),
|
format!("Import `{import_path}`"),
|
||||||
range,
|
range,
|
||||||
|builder| {
|
|builder| {
|
||||||
let scope = match scope.clone() {
|
let scope = match scope.clone() {
|
||||||
|
@ -138,7 +140,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
|
||||||
ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
|
ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
|
||||||
ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
|
ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
|
||||||
};
|
};
|
||||||
insert_use(&scope, mod_path_to_ast(&import.import_path), &ctx.config.insert_use);
|
insert_use(&scope, mod_path_to_ast(&import_path), &ctx.config.insert_use);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,16 +54,17 @@ fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
|
||||||
|
|
||||||
let indent_spaces = indentation.to_string();
|
let indent_spaces = indentation.to_string();
|
||||||
let output = lines
|
let output = lines
|
||||||
.map(|l| l.trim_start_matches(&indent_spaces))
|
.map(|line| {
|
||||||
.map(|l| {
|
let line = line.trim_start_matches(&indent_spaces);
|
||||||
|
|
||||||
// Don't introduce trailing whitespace
|
// Don't introduce trailing whitespace
|
||||||
if l.is_empty() {
|
if line.is_empty() {
|
||||||
line_prefix.to_string()
|
line_prefix.to_string()
|
||||||
} else {
|
} else {
|
||||||
format!("{} {}", line_prefix, l.trim_start_matches(&indent_spaces))
|
format!("{line_prefix} {line}")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.join(&format!("\n{}", indent_spaces));
|
.join(&format!("\n{indent_spaces}"));
|
||||||
|
|
||||||
edit.replace(target, output)
|
edit.replace(target, output)
|
||||||
},
|
},
|
||||||
|
@ -96,7 +97,7 @@ fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
|
||||||
let block_prefix =
|
let block_prefix =
|
||||||
CommentKind { shape: CommentShape::Block, ..comment.kind() }.prefix();
|
CommentKind { shape: CommentShape::Block, ..comment.kind() }.prefix();
|
||||||
|
|
||||||
let output = format!("{}\n{}\n{}*/", block_prefix, block_comment_body, indentation);
|
let output = format!("{block_prefix}\n{block_comment_body}\n{indentation}*/");
|
||||||
|
|
||||||
edit.replace(target, output)
|
edit.replace(target, output)
|
||||||
},
|
},
|
||||||
|
|
|
@ -32,19 +32,19 @@ pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext<'_>
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut converted = match target_radix {
|
let mut converted = match target_radix {
|
||||||
Radix::Binary => format!("0b{:b}", value),
|
Radix::Binary => format!("0b{value:b}"),
|
||||||
Radix::Octal => format!("0o{:o}", value),
|
Radix::Octal => format!("0o{value:o}"),
|
||||||
Radix::Decimal => value.to_string(),
|
Radix::Decimal => value.to_string(),
|
||||||
Radix::Hexadecimal => format!("0x{:X}", value),
|
Radix::Hexadecimal => format!("0x{value:X}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let label = format!("Convert {} to {}{}", literal, converted, suffix.unwrap_or_default());
|
|
||||||
|
|
||||||
// Appends the type suffix back into the new literal if it exists.
|
// Appends the type suffix back into the new literal if it exists.
|
||||||
if let Some(suffix) = suffix {
|
if let Some(suffix) = suffix {
|
||||||
converted.push_str(suffix);
|
converted.push_str(suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let label = format!("Convert {literal} to {converted}");
|
||||||
|
|
||||||
acc.add_group(
|
acc.add_group(
|
||||||
&group_id,
|
&group_id,
|
||||||
AssistId("convert_integer_literal", AssistKind::RefactorInline),
|
AssistId("convert_integer_literal", AssistKind::RefactorInline),
|
||||||
|
|
|
@ -86,9 +86,9 @@ pub(crate) fn convert_into_to_from(acc: &mut Assists, ctx: &AssistContext<'_>) -
|
||||||
impl_.syntax().text_range(),
|
impl_.syntax().text_range(),
|
||||||
|builder| {
|
|builder| {
|
||||||
builder.replace(src_type.syntax().text_range(), dest_type.to_string());
|
builder.replace(src_type.syntax().text_range(), dest_type.to_string());
|
||||||
builder.replace(ast_trait.syntax().text_range(), format!("From<{}>", src_type));
|
builder.replace(ast_trait.syntax().text_range(), format!("From<{src_type}>"));
|
||||||
builder.replace(into_fn_return.syntax().text_range(), "-> Self");
|
builder.replace(into_fn_return.syntax().text_range(), "-> Self");
|
||||||
builder.replace(into_fn_params.syntax().text_range(), format!("(val: {})", src_type));
|
builder.replace(into_fn_params.syntax().text_range(), format!("(val: {src_type})"));
|
||||||
builder.replace(into_fn_name.syntax().text_range(), "from");
|
builder.replace(into_fn_name.syntax().text_range(), "from");
|
||||||
|
|
||||||
for s in selfs {
|
for s in selfs {
|
||||||
|
|
|
@ -119,19 +119,19 @@ pub(crate) fn convert_for_loop_with_for_each(
|
||||||
{
|
{
|
||||||
// We have either "for x in &col" and col implements a method called iter
|
// We have either "for x in &col" and col implements a method called iter
|
||||||
// or "for x in &mut col" and col implements a method called iter_mut
|
// or "for x in &mut col" and col implements a method called iter_mut
|
||||||
format_to!(buf, "{}.{}()", expr_behind_ref, method);
|
format_to!(buf, "{expr_behind_ref}.{method}()");
|
||||||
} else if let ast::Expr::RangeExpr(..) = iterable {
|
} else if let ast::Expr::RangeExpr(..) = iterable {
|
||||||
// range expressions need to be parenthesized for the syntax to be correct
|
// range expressions need to be parenthesized for the syntax to be correct
|
||||||
format_to!(buf, "({})", iterable);
|
format_to!(buf, "({iterable})");
|
||||||
} else if impls_core_iter(&ctx.sema, &iterable) {
|
} else if impls_core_iter(&ctx.sema, &iterable) {
|
||||||
format_to!(buf, "{}", iterable);
|
format_to!(buf, "{iterable}");
|
||||||
} else if let ast::Expr::RefExpr(_) = iterable {
|
} else if let ast::Expr::RefExpr(_) = iterable {
|
||||||
format_to!(buf, "({}).into_iter()", iterable);
|
format_to!(buf, "({iterable}).into_iter()");
|
||||||
} else {
|
} else {
|
||||||
format_to!(buf, "{}.into_iter()", iterable);
|
format_to!(buf, "{iterable}.into_iter()");
|
||||||
}
|
}
|
||||||
|
|
||||||
format_to!(buf, ".for_each(|{}| {});", pat, body);
|
format_to!(buf, ".for_each(|{pat}| {body});");
|
||||||
|
|
||||||
builder.replace(for_loop.syntax().text_range(), buf)
|
builder.replace(for_loop.syntax().text_range(), buf)
|
||||||
},
|
},
|
||||||
|
|
|
@ -80,7 +80,7 @@ fn binders_to_str(binders: &[(Name, bool)], addmut: bool) -> String {
|
||||||
.map(
|
.map(
|
||||||
|(ident, ismut)| {
|
|(ident, ismut)| {
|
||||||
if *ismut && addmut {
|
if *ismut && addmut {
|
||||||
format!("mut {}", ident)
|
format!("mut {ident}")
|
||||||
} else {
|
} else {
|
||||||
ident.to_string()
|
ident.to_string()
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ fn binders_to_str(binders: &[(Name, bool)], addmut: bool) -> String {
|
||||||
} else if binders.len() == 1 {
|
} else if binders.len() == 1 {
|
||||||
vars
|
vars
|
||||||
} else {
|
} else {
|
||||||
format!("({})", vars)
|
format!("({vars})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ pub(crate) fn convert_let_else_to_match(acc: &mut Assists, ctx: &AssistContext<'
|
||||||
|
|
||||||
let only_expr = let_else_block.statements().next().is_none();
|
let only_expr = let_else_block.statements().next().is_none();
|
||||||
let branch2 = match &let_else_block.tail_expr() {
|
let branch2 = match &let_else_block.tail_expr() {
|
||||||
Some(tail) if only_expr => format!("{},", tail.syntax().text()),
|
Some(tail) if only_expr => format!("{tail},"),
|
||||||
_ => let_else_block.syntax().text().to_string(),
|
_ => let_else_block.syntax().text().to_string(),
|
||||||
};
|
};
|
||||||
let replace = if binders.is_empty() {
|
let replace = if binders.is_empty() {
|
||||||
|
|
|
@ -0,0 +1,413 @@
|
||||||
|
use ide_db::defs::{Definition, NameRefClass};
|
||||||
|
use syntax::{
|
||||||
|
ast::{self, HasName},
|
||||||
|
ted, AstNode, SyntaxNode,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
assist_context::{AssistContext, Assists},
|
||||||
|
AssistId, AssistKind,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assist: convert_match_to_let_else
|
||||||
|
//
|
||||||
|
// Converts let statement with match initializer to let-else statement.
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// # //- minicore: option
|
||||||
|
// fn foo(opt: Option<()>) {
|
||||||
|
// let val = $0match opt {
|
||||||
|
// Some(it) => it,
|
||||||
|
// None => return,
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
// ->
|
||||||
|
// ```
|
||||||
|
// fn foo(opt: Option<()>) {
|
||||||
|
// let Some(val) = opt else { return };
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
pub(crate) fn convert_match_to_let_else(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
|
||||||
|
let let_stmt: ast::LetStmt = ctx.find_node_at_offset()?;
|
||||||
|
let binding = find_binding(let_stmt.pat()?)?;
|
||||||
|
|
||||||
|
let initializer = match let_stmt.initializer() {
|
||||||
|
Some(ast::Expr::MatchExpr(it)) => it,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
let initializer_expr = initializer.expr()?;
|
||||||
|
|
||||||
|
let (extracting_arm, diverging_arm) = match find_arms(ctx, &initializer) {
|
||||||
|
Some(it) => it,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
if extracting_arm.guard().is_some() {
|
||||||
|
cov_mark::hit!(extracting_arm_has_guard);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let diverging_arm_expr = diverging_arm.expr()?;
|
||||||
|
let extracting_arm_pat = extracting_arm.pat()?;
|
||||||
|
let extracted_variable = find_extracted_variable(ctx, &extracting_arm)?;
|
||||||
|
|
||||||
|
acc.add(
|
||||||
|
AssistId("convert_match_to_let_else", AssistKind::RefactorRewrite),
|
||||||
|
"Convert match to let-else",
|
||||||
|
let_stmt.syntax().text_range(),
|
||||||
|
|builder| {
|
||||||
|
let extracting_arm_pat = rename_variable(&extracting_arm_pat, extracted_variable, binding);
|
||||||
|
builder.replace(
|
||||||
|
let_stmt.syntax().text_range(),
|
||||||
|
format!("let {extracting_arm_pat} = {initializer_expr} else {{ {diverging_arm_expr} }};")
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a pattern, find the name introduced to the surrounding scope.
|
||||||
|
fn find_binding(pat: ast::Pat) -> Option<ast::IdentPat> {
|
||||||
|
if let ast::Pat::IdentPat(ident) = pat {
|
||||||
|
Some(ident)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a match expression, find extracting and diverging arms.
|
||||||
|
fn find_arms(
|
||||||
|
ctx: &AssistContext<'_>,
|
||||||
|
match_expr: &ast::MatchExpr,
|
||||||
|
) -> Option<(ast::MatchArm, ast::MatchArm)> {
|
||||||
|
let arms = match_expr.match_arm_list()?.arms().collect::<Vec<_>>();
|
||||||
|
if arms.len() != 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut extracting = None;
|
||||||
|
let mut diverging = None;
|
||||||
|
for arm in arms {
|
||||||
|
if ctx.sema.type_of_expr(&arm.expr().unwrap()).unwrap().original().is_never() {
|
||||||
|
diverging = Some(arm);
|
||||||
|
} else {
|
||||||
|
extracting = Some(arm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match (extracting, diverging) {
|
||||||
|
(Some(extracting), Some(diverging)) => Some((extracting, diverging)),
|
||||||
|
_ => {
|
||||||
|
cov_mark::hit!(non_diverging_match);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given an extracting arm, find the extracted variable.
|
||||||
|
fn find_extracted_variable(ctx: &AssistContext<'_>, arm: &ast::MatchArm) -> Option<ast::Name> {
|
||||||
|
match arm.expr()? {
|
||||||
|
ast::Expr::PathExpr(path) => {
|
||||||
|
let name_ref = path.syntax().descendants().find_map(ast::NameRef::cast)?;
|
||||||
|
match NameRefClass::classify(&ctx.sema, &name_ref)? {
|
||||||
|
NameRefClass::Definition(Definition::Local(local)) => {
|
||||||
|
let source = local.source(ctx.db()).value.left()?;
|
||||||
|
Some(source.name()?)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
cov_mark::hit!(extracting_arm_is_not_an_identity_expr);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename `extracted` with `binding` in `pat`.
|
||||||
|
fn rename_variable(pat: &ast::Pat, extracted: ast::Name, binding: ast::IdentPat) -> SyntaxNode {
|
||||||
|
let syntax = pat.syntax().clone_for_update();
|
||||||
|
let extracted_syntax = syntax.covering_element(extracted.syntax().text_range());
|
||||||
|
|
||||||
|
// If `extracted` variable is a record field, we should rename it to `binding`,
|
||||||
|
// otherwise we just need to replace `extracted` with `binding`.
|
||||||
|
|
||||||
|
if let Some(record_pat_field) = extracted_syntax.ancestors().find_map(ast::RecordPatField::cast)
|
||||||
|
{
|
||||||
|
if let Some(name_ref) = record_pat_field.field_name() {
|
||||||
|
ted::replace(
|
||||||
|
record_pat_field.syntax(),
|
||||||
|
ast::make::record_pat_field(ast::make::name_ref(&name_ref.text()), binding.into())
|
||||||
|
.syntax()
|
||||||
|
.clone_for_update(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ted::replace(extracted_syntax, binding.syntax().clone_for_update());
|
||||||
|
}
|
||||||
|
|
||||||
|
syntax
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_not_be_applicable_for_non_diverging_match() {
|
||||||
|
cov_mark::check!(non_diverging_match);
|
||||||
|
check_assist_not_applicable(
|
||||||
|
convert_match_to_let_else,
|
||||||
|
r#"
|
||||||
|
//- minicore: option
|
||||||
|
fn foo(opt: Option<()>) {
|
||||||
|
let val = $0match opt {
|
||||||
|
Some(it) => it,
|
||||||
|
None => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_not_be_applicable_if_extracting_arm_is_not_an_identity_expr() {
|
||||||
|
cov_mark::check_count!(extracting_arm_is_not_an_identity_expr, 2);
|
||||||
|
check_assist_not_applicable(
|
||||||
|
convert_match_to_let_else,
|
||||||
|
r#"
|
||||||
|
//- minicore: option
|
||||||
|
fn foo(opt: Option<i32>) {
|
||||||
|
let val = $0match opt {
|
||||||
|
Some(it) => it + 1,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
check_assist_not_applicable(
|
||||||
|
convert_match_to_let_else,
|
||||||
|
r#"
|
||||||
|
//- minicore: option
|
||||||
|
fn foo(opt: Option<()>) {
|
||||||
|
let val = $0match opt {
|
||||||
|
Some(it) => {
|
||||||
|
let _ = 1 + 1;
|
||||||
|
it
|
||||||
|
},
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_not_be_applicable_if_extracting_arm_has_guard() {
|
||||||
|
cov_mark::check!(extracting_arm_has_guard);
|
||||||
|
check_assist_not_applicable(
|
||||||
|
convert_match_to_let_else,
|
||||||
|
r#"
|
||||||
|
//- minicore: option
|
||||||
|
fn foo(opt: Option<()>) {
|
||||||
|
let val = $0match opt {
|
||||||
|
Some(it) if 2 > 1 => it,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic_pattern() {
|
||||||
|
check_assist(
|
||||||
|
convert_match_to_let_else,
|
||||||
|
r#"
|
||||||
|
//- minicore: option
|
||||||
|
fn foo(opt: Option<()>) {
|
||||||
|
let val = $0match opt {
|
||||||
|
Some(it) => it,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
fn foo(opt: Option<()>) {
|
||||||
|
let Some(val) = opt else { return };
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keeps_modifiers() {
|
||||||
|
check_assist(
|
||||||
|
convert_match_to_let_else,
|
||||||
|
r#"
|
||||||
|
//- minicore: option
|
||||||
|
fn foo(opt: Option<()>) {
|
||||||
|
let ref mut val = $0match opt {
|
||||||
|
Some(it) => it,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
fn foo(opt: Option<()>) {
|
||||||
|
let Some(ref mut val) = opt else { return };
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_pattern() {
|
||||||
|
check_assist(
|
||||||
|
convert_match_to_let_else,
|
||||||
|
r#"
|
||||||
|
//- minicore: option, result
|
||||||
|
fn foo(opt: Option<Result<()>>) {
|
||||||
|
let val = $0match opt {
|
||||||
|
Some(Ok(it)) => it,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
fn foo(opt: Option<Result<()>>) {
|
||||||
|
let Some(Ok(val)) = opt else { return };
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn works_with_any_diverging_block() {
|
||||||
|
check_assist(
|
||||||
|
convert_match_to_let_else,
|
||||||
|
r#"
|
||||||
|
//- minicore: option
|
||||||
|
fn foo(opt: Option<()>) {
|
||||||
|
loop {
|
||||||
|
let val = $0match opt {
|
||||||
|
Some(it) => it,
|
||||||
|
None => break,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
fn foo(opt: Option<()>) {
|
||||||
|
loop {
|
||||||
|
let Some(val) = opt else { break };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
check_assist(
|
||||||
|
convert_match_to_let_else,
|
||||||
|
r#"
|
||||||
|
//- minicore: option
|
||||||
|
fn foo(opt: Option<()>) {
|
||||||
|
loop {
|
||||||
|
let val = $0match opt {
|
||||||
|
Some(it) => it,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
fn foo(opt: Option<()>) {
|
||||||
|
loop {
|
||||||
|
let Some(val) = opt else { continue };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
check_assist(
|
||||||
|
convert_match_to_let_else,
|
||||||
|
r#"
|
||||||
|
//- minicore: option
|
||||||
|
fn panic() -> ! {}
|
||||||
|
|
||||||
|
fn foo(opt: Option<()>) {
|
||||||
|
loop {
|
||||||
|
let val = $0match opt {
|
||||||
|
Some(it) => it,
|
||||||
|
None => panic(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
fn panic() -> ! {}
|
||||||
|
|
||||||
|
fn foo(opt: Option<()>) {
|
||||||
|
loop {
|
||||||
|
let Some(val) = opt else { panic() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn struct_pattern() {
|
||||||
|
check_assist(
|
||||||
|
convert_match_to_let_else,
|
||||||
|
r#"
|
||||||
|
//- minicore: option
|
||||||
|
struct Point {
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn foo(opt: Option<Point>) {
|
||||||
|
let val = $0match opt {
|
||||||
|
Some(Point { x: 0, y }) => y,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
struct Point {
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn foo(opt: Option<Point>) {
|
||||||
|
let Some(Point { x: 0, y: val }) = opt else { return };
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renames_whole_binding() {
|
||||||
|
check_assist(
|
||||||
|
convert_match_to_let_else,
|
||||||
|
r#"
|
||||||
|
//- minicore: option
|
||||||
|
fn foo(opt: Option<i32>) -> Option<i32> {
|
||||||
|
let val = $0match opt {
|
||||||
|
it @ Some(42) => it,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
val
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
fn foo(opt: Option<i32>) -> Option<i32> {
|
||||||
|
let val @ Some(42) = opt else { return None };
|
||||||
|
val
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -129,32 +129,15 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext<'
|
||||||
}
|
}
|
||||||
Some((path, bound_ident)) => {
|
Some((path, bound_ident)) => {
|
||||||
// If-let.
|
// If-let.
|
||||||
let match_expr = {
|
let pat = make::tuple_struct_pat(path, once(bound_ident));
|
||||||
let happy_arm = {
|
let let_else_stmt = make::let_else_stmt(
|
||||||
let pat = make::tuple_struct_pat(
|
pat.into(),
|
||||||
path,
|
|
||||||
once(make::ext::simple_ident_pat(make::name("it")).into()),
|
|
||||||
);
|
|
||||||
let expr = {
|
|
||||||
let path = make::ext::ident_path("it");
|
|
||||||
make::expr_path(path)
|
|
||||||
};
|
|
||||||
make::match_arm(once(pat.into()), None, expr)
|
|
||||||
};
|
|
||||||
|
|
||||||
let sad_arm = make::match_arm(
|
|
||||||
// FIXME: would be cool to use `None` or `Err(_)` if appropriate
|
|
||||||
once(make::wildcard_pat().into()),
|
|
||||||
None,
|
None,
|
||||||
early_expression,
|
cond_expr,
|
||||||
|
ast::make::tail_only_block_expr(early_expression),
|
||||||
);
|
);
|
||||||
|
let let_else_stmt = let_else_stmt.indent(if_indent_level);
|
||||||
make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
|
let_else_stmt.syntax().clone_for_update()
|
||||||
};
|
|
||||||
|
|
||||||
let let_stmt = make::let_stmt(bound_ident, None, Some(match_expr));
|
|
||||||
let let_stmt = let_stmt.indent(if_indent_level);
|
|
||||||
let_stmt.syntax().clone_for_update()
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -238,10 +221,7 @@ fn main(n: Option<String>) {
|
||||||
r#"
|
r#"
|
||||||
fn main(n: Option<String>) {
|
fn main(n: Option<String>) {
|
||||||
bar();
|
bar();
|
||||||
let n = match n {
|
let Some(n) = n else { return };
|
||||||
Some(it) => it,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
foo(n);
|
foo(n);
|
||||||
|
|
||||||
// comment
|
// comment
|
||||||
|
@ -264,10 +244,7 @@ fn main() {
|
||||||
"#,
|
"#,
|
||||||
r#"
|
r#"
|
||||||
fn main() {
|
fn main() {
|
||||||
let x = match Err(92) {
|
let Ok(x) = Err(92) else { return };
|
||||||
Ok(it) => it,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
foo(x);
|
foo(x);
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
|
@ -292,10 +269,7 @@ fn main(n: Option<String>) {
|
||||||
r#"
|
r#"
|
||||||
fn main(n: Option<String>) {
|
fn main(n: Option<String>) {
|
||||||
bar();
|
bar();
|
||||||
let n = match n {
|
let Some(n) = n else { return };
|
||||||
Some(it) => it,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
foo(n);
|
foo(n);
|
||||||
|
|
||||||
// comment
|
// comment
|
||||||
|
@ -323,10 +297,7 @@ fn main(n: Option<String>) {
|
||||||
r#"
|
r#"
|
||||||
fn main(n: Option<String>) {
|
fn main(n: Option<String>) {
|
||||||
bar();
|
bar();
|
||||||
let mut n = match n {
|
let Some(mut n) = n else { return };
|
||||||
Some(it) => it,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
foo(n);
|
foo(n);
|
||||||
|
|
||||||
// comment
|
// comment
|
||||||
|
@ -354,10 +325,7 @@ fn main(n: Option<&str>) {
|
||||||
r#"
|
r#"
|
||||||
fn main(n: Option<&str>) {
|
fn main(n: Option<&str>) {
|
||||||
bar();
|
bar();
|
||||||
let ref n = match n {
|
let Some(ref n) = n else { return };
|
||||||
Some(it) => it,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
foo(n);
|
foo(n);
|
||||||
|
|
||||||
// comment
|
// comment
|
||||||
|
@ -412,10 +380,7 @@ fn main() {
|
||||||
r#"
|
r#"
|
||||||
fn main() {
|
fn main() {
|
||||||
while true {
|
while true {
|
||||||
let n = match n {
|
let Some(n) = n else { continue };
|
||||||
Some(it) => it,
|
|
||||||
_ => continue,
|
|
||||||
};
|
|
||||||
foo(n);
|
foo(n);
|
||||||
bar();
|
bar();
|
||||||
}
|
}
|
||||||
|
@ -469,10 +434,7 @@ fn main() {
|
||||||
r#"
|
r#"
|
||||||
fn main() {
|
fn main() {
|
||||||
loop {
|
loop {
|
||||||
let n = match n {
|
let Some(n) = n else { continue };
|
||||||
Some(it) => it,
|
|
||||||
_ => continue,
|
|
||||||
};
|
|
||||||
foo(n);
|
foo(n);
|
||||||
bar();
|
bar();
|
||||||
}
|
}
|
||||||
|
|
|
@ -226,7 +226,13 @@ fn edit_field_references(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_names(fields: impl Iterator<Item = ast::TupleField>) -> Vec<ast::Name> {
|
fn generate_names(fields: impl Iterator<Item = ast::TupleField>) -> Vec<ast::Name> {
|
||||||
fields.enumerate().map(|(i, _)| ast::make::name(&format!("field{}", i + 1))).collect()
|
fields
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, _)| {
|
||||||
|
let idx = i + 1;
|
||||||
|
ast::make::name(&format!("field{idx}"))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -58,16 +58,16 @@ pub(crate) fn convert_two_arm_bool_match_to_matches_macro(
|
||||||
target_range,
|
target_range,
|
||||||
|builder| {
|
|builder| {
|
||||||
let mut arm_str = String::new();
|
let mut arm_str = String::new();
|
||||||
if let Some(ref pat) = first_arm.pat() {
|
if let Some(pat) = &first_arm.pat() {
|
||||||
arm_str += &pat.to_string();
|
arm_str += &pat.to_string();
|
||||||
}
|
}
|
||||||
if let Some(ref guard) = first_arm.guard() {
|
if let Some(guard) = &first_arm.guard() {
|
||||||
arm_str += &format!(" {}", &guard.to_string());
|
arm_str += &format!(" {guard}");
|
||||||
}
|
}
|
||||||
if invert_matches {
|
if invert_matches {
|
||||||
builder.replace(target_range, format!("!matches!({}, {})", expr, arm_str));
|
builder.replace(target_range, format!("!matches!({expr}, {arm_str})"));
|
||||||
} else {
|
} else {
|
||||||
builder.replace(target_range, format!("matches!({}, {})", expr, arm_str));
|
builder.replace(target_range, format!("matches!({expr}, {arm_str})"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -133,7 +133,7 @@ fn generate_name(
|
||||||
_usages: &Option<UsageSearchResult>,
|
_usages: &Option<UsageSearchResult>,
|
||||||
) -> String {
|
) -> String {
|
||||||
// FIXME: detect if name already used
|
// FIXME: detect if name already used
|
||||||
format!("_{}", index)
|
format!("_{index}")
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RefType {
|
enum RefType {
|
||||||
|
@ -168,12 +168,12 @@ fn edit_tuple_assignment(
|
||||||
let add_cursor = |text: &str| {
|
let add_cursor = |text: &str| {
|
||||||
// place cursor on first tuple item
|
// place cursor on first tuple item
|
||||||
let first_tuple = &data.field_names[0];
|
let first_tuple = &data.field_names[0];
|
||||||
text.replacen(first_tuple, &format!("$0{}", first_tuple), 1)
|
text.replacen(first_tuple, &format!("$0{first_tuple}"), 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
// with sub_pattern: keep original tuple and add subpattern: `tup @ (_0, _1)`
|
// with sub_pattern: keep original tuple and add subpattern: `tup @ (_0, _1)`
|
||||||
if in_sub_pattern {
|
if in_sub_pattern {
|
||||||
let text = format!(" @ {}", tuple_pat);
|
let text = format!(" @ {tuple_pat}");
|
||||||
match ctx.config.snippet_cap {
|
match ctx.config.snippet_cap {
|
||||||
Some(cap) => {
|
Some(cap) => {
|
||||||
let snip = add_cursor(&text);
|
let snip = add_cursor(&text);
|
||||||
|
@ -314,9 +314,9 @@ struct RefData {
|
||||||
impl RefData {
|
impl RefData {
|
||||||
fn format(&self, field_name: &str) -> String {
|
fn format(&self, field_name: &str) -> String {
|
||||||
match (self.needs_deref, self.needs_parentheses) {
|
match (self.needs_deref, self.needs_parentheses) {
|
||||||
(true, true) => format!("(*{})", field_name),
|
(true, true) => format!("(*{field_name})"),
|
||||||
(true, false) => format!("*{}", field_name),
|
(true, false) => format!("*{field_name}"),
|
||||||
(false, true) => format!("({})", field_name),
|
(false, true) => format!("({field_name})"),
|
||||||
(false, false) => field_name.to_string(),
|
(false, false) => field_name.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,7 @@ fn make_function_name(semantics_scope: &hir::SemanticsScope<'_>) -> ast::NameRef
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
while names_in_scope.contains(&name) {
|
while names_in_scope.contains(&name) {
|
||||||
counter += 1;
|
counter += 1;
|
||||||
name = format!("{}{}", &default_name, counter)
|
name = format!("{default_name}{counter}")
|
||||||
}
|
}
|
||||||
make::name_ref(&name)
|
make::name_ref(&name)
|
||||||
}
|
}
|
||||||
|
@ -1291,19 +1291,23 @@ fn make_call(ctx: &AssistContext<'_>, fun: &Function, indent: IndentLevel) -> St
|
||||||
match fun.outliving_locals.as_slice() {
|
match fun.outliving_locals.as_slice() {
|
||||||
[] => {}
|
[] => {}
|
||||||
[var] => {
|
[var] => {
|
||||||
format_to!(buf, "let {}{} = ", mut_modifier(var), var.local.name(ctx.db()))
|
let modifier = mut_modifier(var);
|
||||||
|
let name = var.local.name(ctx.db());
|
||||||
|
format_to!(buf, "let {modifier}{name} = ")
|
||||||
}
|
}
|
||||||
vars => {
|
vars => {
|
||||||
buf.push_str("let (");
|
buf.push_str("let (");
|
||||||
let bindings = vars.iter().format_with(", ", |local, f| {
|
let bindings = vars.iter().format_with(", ", |local, f| {
|
||||||
f(&format_args!("{}{}", mut_modifier(local), local.local.name(ctx.db())))
|
let modifier = mut_modifier(local);
|
||||||
|
let name = local.local.name(ctx.db());
|
||||||
|
f(&format_args!("{modifier}{name}"))
|
||||||
});
|
});
|
||||||
format_to!(buf, "{}", bindings);
|
format_to!(buf, "{bindings}");
|
||||||
buf.push_str(") = ");
|
buf.push_str(") = ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
format_to!(buf, "{}", expr);
|
format_to!(buf, "{expr}");
|
||||||
let insert_comma = fun
|
let insert_comma = fun
|
||||||
.body
|
.body
|
||||||
.parent()
|
.parent()
|
||||||
|
@ -1447,6 +1451,8 @@ fn format_function(
|
||||||
new_indent: IndentLevel,
|
new_indent: IndentLevel,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut fn_def = String::new();
|
let mut fn_def = String::new();
|
||||||
|
|
||||||
|
let fun_name = &fun.name;
|
||||||
let params = fun.make_param_list(ctx, module);
|
let params = fun.make_param_list(ctx, module);
|
||||||
let ret_ty = fun.make_ret_ty(ctx, module);
|
let ret_ty = fun.make_ret_ty(ctx, module);
|
||||||
let body = make_body(ctx, old_indent, new_indent, fun);
|
let body = make_body(ctx, old_indent, new_indent, fun);
|
||||||
|
@ -1454,42 +1460,28 @@ fn format_function(
|
||||||
let async_kw = if fun.control_flow.is_async { "async " } else { "" };
|
let async_kw = if fun.control_flow.is_async { "async " } else { "" };
|
||||||
let unsafe_kw = if fun.control_flow.is_unsafe { "unsafe " } else { "" };
|
let unsafe_kw = if fun.control_flow.is_unsafe { "unsafe " } else { "" };
|
||||||
let (generic_params, where_clause) = make_generic_params_and_where_clause(ctx, fun);
|
let (generic_params, where_clause) = make_generic_params_and_where_clause(ctx, fun);
|
||||||
|
|
||||||
|
format_to!(fn_def, "\n\n{new_indent}{const_kw}{async_kw}{unsafe_kw}");
|
||||||
match ctx.config.snippet_cap {
|
match ctx.config.snippet_cap {
|
||||||
Some(_) => format_to!(
|
Some(_) => format_to!(fn_def, "fn $0{fun_name}"),
|
||||||
fn_def,
|
None => format_to!(fn_def, "fn {fun_name}"),
|
||||||
"\n\n{}{}{}{}fn $0{}",
|
|
||||||
new_indent,
|
|
||||||
const_kw,
|
|
||||||
async_kw,
|
|
||||||
unsafe_kw,
|
|
||||||
fun.name,
|
|
||||||
),
|
|
||||||
None => format_to!(
|
|
||||||
fn_def,
|
|
||||||
"\n\n{}{}{}{}fn {}",
|
|
||||||
new_indent,
|
|
||||||
const_kw,
|
|
||||||
async_kw,
|
|
||||||
unsafe_kw,
|
|
||||||
fun.name,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(generic_params) = generic_params {
|
if let Some(generic_params) = generic_params {
|
||||||
format_to!(fn_def, "{}", generic_params);
|
format_to!(fn_def, "{generic_params}");
|
||||||
}
|
}
|
||||||
|
|
||||||
format_to!(fn_def, "{}", params);
|
format_to!(fn_def, "{params}");
|
||||||
|
|
||||||
if let Some(ret_ty) = ret_ty {
|
if let Some(ret_ty) = ret_ty {
|
||||||
format_to!(fn_def, " {}", ret_ty);
|
format_to!(fn_def, " {ret_ty}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(where_clause) = where_clause {
|
if let Some(where_clause) = where_clause {
|
||||||
format_to!(fn_def, " {}", where_clause);
|
format_to!(fn_def, " {where_clause}");
|
||||||
}
|
}
|
||||||
|
|
||||||
format_to!(fn_def, " {}", body);
|
format_to!(fn_def, " {body}");
|
||||||
|
|
||||||
fn_def
|
fn_def
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,7 +127,7 @@ pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
|
||||||
for item in items_to_be_processed {
|
for item in items_to_be_processed {
|
||||||
let item = item.indent(IndentLevel(1));
|
let item = item.indent(IndentLevel(1));
|
||||||
let mut indented_item = String::new();
|
let mut indented_item = String::new();
|
||||||
format_to!(indented_item, "{}{}", new_item_indent, item.to_string());
|
format_to!(indented_item, "{new_item_indent}{item}");
|
||||||
body_items.push(indented_item);
|
body_items.push(indented_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,30 +137,28 @@ pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
|
||||||
let mut impl_body_def = String::new();
|
let mut impl_body_def = String::new();
|
||||||
|
|
||||||
if let Some(self_ty) = impl_.self_ty() {
|
if let Some(self_ty) = impl_.self_ty() {
|
||||||
|
{
|
||||||
|
let impl_indent = old_item_indent + 1;
|
||||||
format_to!(
|
format_to!(
|
||||||
impl_body_def,
|
impl_body_def,
|
||||||
"{}impl {} {{\n{}\n{}}}",
|
"{impl_indent}impl {self_ty} {{\n{body}\n{impl_indent}}}",
|
||||||
old_item_indent + 1,
|
|
||||||
self_ty.to_string(),
|
|
||||||
body,
|
|
||||||
old_item_indent + 1
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
body = impl_body_def;
|
body = impl_body_def;
|
||||||
|
|
||||||
// Add the import for enum/struct corresponding to given impl block
|
// Add the import for enum/struct corresponding to given impl block
|
||||||
module.make_use_stmt_of_node_with_super(self_ty.syntax());
|
module.make_use_stmt_of_node_with_super(self_ty.syntax());
|
||||||
for item in module.use_items {
|
for item in module.use_items {
|
||||||
let mut indented_item = String::new();
|
let item_indent = old_item_indent + 1;
|
||||||
format_to!(indented_item, "{}{}", old_item_indent + 1, item.to_string());
|
body = format!("{item_indent}{item}\n\n{body}");
|
||||||
body = format!("{}\n\n{}", indented_item, body);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut module_def = String::new();
|
let mut module_def = String::new();
|
||||||
|
|
||||||
format_to!(module_def, "mod {} {{\n{}\n{}}}", module.name, body, old_item_indent);
|
let module_name = module.name;
|
||||||
|
format_to!(module_def, "mod {module_name} {{\n{body}\n{old_item_indent}}}");
|
||||||
|
|
||||||
let mut usages_to_be_updated_for_curr_file = vec![];
|
let mut usages_to_be_updated_for_curr_file = vec![];
|
||||||
for usages_to_be_updated_for_file in usages_to_be_processed {
|
for usages_to_be_updated_for_file in usages_to_be_processed {
|
||||||
|
@ -199,7 +197,7 @@ pub(crate) fn extract_module(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
|
||||||
builder.delete(range);
|
builder.delete(range);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.insert(impl_.syntax().text_range().end(), format!("\n\n{}", module_def));
|
builder.insert(impl_.syntax().text_range().end(), format!("\n\n{module_def}"));
|
||||||
} else {
|
} else {
|
||||||
builder.replace(module.text_range, module_def)
|
builder.replace(module.text_range, module_def)
|
||||||
}
|
}
|
||||||
|
@ -343,9 +341,10 @@ impl Module {
|
||||||
&& !self.text_range.contains_range(desc.text_range())
|
&& !self.text_range.contains_range(desc.text_range())
|
||||||
{
|
{
|
||||||
if let Some(name_ref) = ast::NameRef::cast(desc) {
|
if let Some(name_ref) = ast::NameRef::cast(desc) {
|
||||||
|
let mod_name = self.name;
|
||||||
return Some((
|
return Some((
|
||||||
name_ref.syntax().text_range(),
|
name_ref.syntax().text_range(),
|
||||||
format!("{}::{}", self.name, name_ref),
|
format!("{mod_name}::{name_ref}"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -296,10 +296,14 @@ fn create_struct_def(
|
||||||
|
|
||||||
fn update_variant(variant: &ast::Variant, generics: Option<ast::GenericParamList>) -> Option<()> {
|
fn update_variant(variant: &ast::Variant, generics: Option<ast::GenericParamList>) -> Option<()> {
|
||||||
let name = variant.name()?;
|
let name = variant.name()?;
|
||||||
let ty = generics
|
let generic_args = generics
|
||||||
.filter(|generics| generics.generic_params().count() > 0)
|
.filter(|generics| generics.generic_params().count() > 0)
|
||||||
.map(|generics| make::ty(&format!("{}{}", &name.text(), generics.to_generic_args())))
|
.map(|generics| generics.to_generic_args());
|
||||||
.unwrap_or_else(|| make::ty(&name.text()));
|
// FIXME: replace with a `ast::make` constructor
|
||||||
|
let ty = match generic_args {
|
||||||
|
Some(generic_args) => make::ty(&format!("{name}{generic_args}")),
|
||||||
|
None => make::ty(&name.text()),
|
||||||
|
};
|
||||||
|
|
||||||
// change from a record to a tuple field list
|
// change from a record to a tuple field list
|
||||||
let tuple_field = make::tuple_field(None, ty);
|
let tuple_field = make::tuple_field(None, ty);
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use ide_db::syntax_helpers::node_ext::walk_ty;
|
use ide_db::syntax_helpers::node_ext::walk_ty;
|
||||||
use itertools::Itertools;
|
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, edit::IndentLevel, AstNode, HasGenericParams, HasName},
|
ast::{self, edit::IndentLevel, make, AstNode, HasGenericParams, HasName},
|
||||||
match_ast,
|
match_ast,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -64,41 +63,29 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) ->
|
||||||
known_generics.extend(it.generic_params());
|
known_generics.extend(it.generic_params());
|
||||||
}
|
}
|
||||||
let generics = collect_used_generics(&ty, &known_generics);
|
let generics = collect_used_generics(&ty, &known_generics);
|
||||||
|
let generic_params =
|
||||||
|
generics.map(|it| make::generic_param_list(it.into_iter().cloned()));
|
||||||
|
|
||||||
let replacement = if !generics.is_empty() {
|
let ty_args = generic_params
|
||||||
format!(
|
.as_ref()
|
||||||
"Type<{}>",
|
.map_or(String::new(), |it| it.to_generic_args().to_string());
|
||||||
generics.iter().format_with(", ", |generic, f| {
|
let replacement = format!("Type{ty_args}");
|
||||||
match generic {
|
|
||||||
ast::GenericParam::ConstParam(cp) => f(&cp.name().unwrap()),
|
|
||||||
ast::GenericParam::LifetimeParam(lp) => f(&lp.lifetime().unwrap()),
|
|
||||||
ast::GenericParam::TypeParam(tp) => f(&tp.name().unwrap()),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
String::from("Type")
|
|
||||||
};
|
|
||||||
builder.replace(target, replacement);
|
builder.replace(target, replacement);
|
||||||
|
|
||||||
let indent = IndentLevel::from_node(node);
|
let indent = IndentLevel::from_node(node);
|
||||||
let generics = if !generics.is_empty() {
|
let generic_params = generic_params.map_or(String::new(), |it| it.to_string());
|
||||||
format!("<{}>", generics.iter().format(", "))
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
match ctx.config.snippet_cap {
|
match ctx.config.snippet_cap {
|
||||||
Some(cap) => {
|
Some(cap) => {
|
||||||
builder.insert_snippet(
|
builder.insert_snippet(
|
||||||
cap,
|
cap,
|
||||||
insert_pos,
|
insert_pos,
|
||||||
format!("type $0Type{} = {};\n\n{}", generics, ty, indent),
|
format!("type $0Type{generic_params} = {ty};\n\n{indent}"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
builder.insert(
|
builder.insert(
|
||||||
insert_pos,
|
insert_pos,
|
||||||
format!("type Type{} = {};\n\n{}", generics, ty, indent),
|
format!("type Type{generic_params} = {ty};\n\n{indent}"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,7 +96,7 @@ pub(crate) fn extract_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) ->
|
||||||
fn collect_used_generics<'gp>(
|
fn collect_used_generics<'gp>(
|
||||||
ty: &ast::Type,
|
ty: &ast::Type,
|
||||||
known_generics: &'gp [ast::GenericParam],
|
known_generics: &'gp [ast::GenericParam],
|
||||||
) -> Vec<&'gp ast::GenericParam> {
|
) -> Option<Vec<&'gp ast::GenericParam>> {
|
||||||
// can't use a closure -> closure here cause lifetime inference fails for that
|
// can't use a closure -> closure here cause lifetime inference fails for that
|
||||||
fn find_lifetime(text: &str) -> impl Fn(&&ast::GenericParam) -> bool + '_ {
|
fn find_lifetime(text: &str) -> impl Fn(&&ast::GenericParam) -> bool + '_ {
|
||||||
move |gp: &&ast::GenericParam| match gp {
|
move |gp: &&ast::GenericParam| match gp {
|
||||||
|
@ -198,7 +185,8 @@ fn collect_used_generics<'gp>(
|
||||||
ast::GenericParam::LifetimeParam(_) => 0,
|
ast::GenericParam::LifetimeParam(_) => 0,
|
||||||
ast::GenericParam::TypeParam(_) => 1,
|
ast::GenericParam::TypeParam(_) => 1,
|
||||||
});
|
});
|
||||||
generics
|
|
||||||
|
Some(generics).filter(|it| it.len() > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -91,13 +91,13 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
|
||||||
|
|
||||||
match anchor {
|
match anchor {
|
||||||
Anchor::Before(_) | Anchor::Replace(_) => {
|
Anchor::Before(_) | Anchor::Replace(_) => {
|
||||||
format_to!(buf, "let {}{} = {}", var_modifier, var_name, reference_modifier)
|
format_to!(buf, "let {var_modifier}{var_name} = {reference_modifier}")
|
||||||
}
|
}
|
||||||
Anchor::WrapInBlock(_) => {
|
Anchor::WrapInBlock(_) => {
|
||||||
format_to!(buf, "{{ let {} = {}", var_name, reference_modifier)
|
format_to!(buf, "{{ let {var_name} = {reference_modifier}")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
format_to!(buf, "{}", to_extract.syntax());
|
format_to!(buf, "{to_extract}");
|
||||||
|
|
||||||
if let Anchor::Replace(stmt) = anchor {
|
if let Anchor::Replace(stmt) = anchor {
|
||||||
cov_mark::hit!(test_extract_var_expr_stmt);
|
cov_mark::hit!(test_extract_var_expr_stmt);
|
||||||
|
@ -107,8 +107,8 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
|
||||||
match ctx.config.snippet_cap {
|
match ctx.config.snippet_cap {
|
||||||
Some(cap) => {
|
Some(cap) => {
|
||||||
let snip = buf.replace(
|
let snip = buf.replace(
|
||||||
&format!("let {}{}", var_modifier, var_name),
|
&format!("let {var_modifier}{var_name}"),
|
||||||
&format!("let {}$0{}", var_modifier, var_name),
|
&format!("let {var_modifier}$0{var_name}"),
|
||||||
);
|
);
|
||||||
edit.replace_snippet(cap, expr_range, snip)
|
edit.replace_snippet(cap, expr_range, snip)
|
||||||
}
|
}
|
||||||
|
@ -135,8 +135,8 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
|
||||||
match ctx.config.snippet_cap {
|
match ctx.config.snippet_cap {
|
||||||
Some(cap) => {
|
Some(cap) => {
|
||||||
let snip = buf.replace(
|
let snip = buf.replace(
|
||||||
&format!("let {}{}", var_modifier, var_name),
|
&format!("let {var_modifier}{var_name}"),
|
||||||
&format!("let {}$0{}", var_modifier, var_name),
|
&format!("let {var_modifier}$0{var_name}"),
|
||||||
);
|
);
|
||||||
edit.insert_snippet(cap, offset, snip)
|
edit.insert_snippet(cap, offset, snip)
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,8 +57,8 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||||
if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
|
if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
|
||||||
|
|
||||||
let assist_label = match target_name {
|
let assist_label = match target_name {
|
||||||
None => format!("Change visibility to {}", missing_visibility),
|
None => format!("Change visibility to {missing_visibility}"),
|
||||||
Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
|
Some(name) => format!("Change visibility of {name} to {missing_visibility}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
|
acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
|
||||||
|
@ -68,15 +68,15 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||||
Some(current_visibility) => builder.replace_snippet(
|
Some(current_visibility) => builder.replace_snippet(
|
||||||
cap,
|
cap,
|
||||||
current_visibility.syntax().text_range(),
|
current_visibility.syntax().text_range(),
|
||||||
format!("$0{}", missing_visibility),
|
format!("$0{missing_visibility}"),
|
||||||
),
|
),
|
||||||
None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
|
None => builder.insert_snippet(cap, offset, format!("$0{missing_visibility} ")),
|
||||||
},
|
},
|
||||||
None => match current_visibility {
|
None => match current_visibility {
|
||||||
Some(current_visibility) => {
|
Some(current_visibility) => {
|
||||||
builder.replace(current_visibility.syntax().text_range(), missing_visibility)
|
builder.replace(current_visibility.syntax().text_range(), missing_visibility)
|
||||||
}
|
}
|
||||||
None => builder.insert(offset, format!("{} ", missing_visibility)),
|
None => builder.insert(offset, format!("{missing_visibility} ")),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -114,7 +114,7 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext<'_>
|
||||||
|
|
||||||
let target_name = record_field_def.name(ctx.db());
|
let target_name = record_field_def.name(ctx.db());
|
||||||
let assist_label =
|
let assist_label =
|
||||||
format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
|
format!("Change visibility of {parent_name}.{target_name} to {missing_visibility}");
|
||||||
|
|
||||||
acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
|
acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
|
||||||
builder.edit_file(target_file);
|
builder.edit_file(target_file);
|
||||||
|
@ -123,15 +123,15 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext<'_>
|
||||||
Some(current_visibility) => builder.replace_snippet(
|
Some(current_visibility) => builder.replace_snippet(
|
||||||
cap,
|
cap,
|
||||||
current_visibility.syntax().text_range(),
|
current_visibility.syntax().text_range(),
|
||||||
format!("$0{}", missing_visibility),
|
format!("$0{missing_visibility}"),
|
||||||
),
|
),
|
||||||
None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
|
None => builder.insert_snippet(cap, offset, format!("$0{missing_visibility} ")),
|
||||||
},
|
},
|
||||||
None => match current_visibility {
|
None => match current_visibility {
|
||||||
Some(current_visibility) => {
|
Some(current_visibility) => {
|
||||||
builder.replace(current_visibility.syntax().text_range(), missing_visibility)
|
builder.replace(current_visibility.syntax().text_range(), missing_visibility)
|
||||||
}
|
}
|
||||||
None => builder.insert(offset, format!("{} ", missing_visibility)),
|
None => builder.insert(offset, format!("{missing_visibility} ")),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -124,6 +124,7 @@ fn generate_enum_projection_method(
|
||||||
happy_case,
|
happy_case,
|
||||||
sad_case,
|
sad_case,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
let variant = ctx.find_node_at_offset::<ast::Variant>()?;
|
let variant = ctx.find_node_at_offset::<ast::Variant>()?;
|
||||||
let variant_name = variant.name()?;
|
let variant_name = variant.name()?;
|
||||||
let parent_enum = ast::Adt::Enum(variant.parent_enum());
|
let parent_enum = ast::Adt::Enum(variant.parent_enum());
|
||||||
|
@ -144,7 +145,7 @@ fn generate_enum_projection_method(
|
||||||
ast::StructKind::Unit => return None,
|
ast::StructKind::Unit => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let fn_name = format!("{}_{}", fn_name_prefix, &to_lower_snake_case(&variant_name.text()));
|
let fn_name = format!("{fn_name_prefix}_{}", &to_lower_snake_case(&variant_name.text()));
|
||||||
|
|
||||||
// Return early if we've found an existing new fn
|
// Return early if we've found an existing new fn
|
||||||
let impl_def = find_struct_impl(ctx, &parent_enum, &[fn_name.clone()])?;
|
let impl_def = find_struct_impl(ctx, &parent_enum, &[fn_name.clone()])?;
|
||||||
|
@ -156,15 +157,25 @@ fn generate_enum_projection_method(
|
||||||
assist_description,
|
assist_description,
|
||||||
target,
|
target,
|
||||||
|builder| {
|
|builder| {
|
||||||
let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{v} "));
|
let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v));
|
||||||
|
|
||||||
|
let field_type_syntax = field_type.syntax();
|
||||||
|
|
||||||
|
let must_use = if ctx.config.assist_emit_must_use {
|
||||||
|
"#[must_use]\n "
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
|
||||||
let method = format!(
|
let method = format!(
|
||||||
" {vis}fn {fn_name}({self_param}) -> {return_prefix}{field_type}{return_suffix} {{
|
" {must_use}{vis}fn {fn_name}({self_param}) -> {return_prefix}{field_type_syntax}{return_suffix} {{
|
||||||
if let Self::{variant_name}{pattern_suffix} = self {{
|
if let Self::{variant_name}{pattern_suffix} = self {{
|
||||||
{happy_case}({bound_name})
|
{happy_case}({bound_name})
|
||||||
}} else {{
|
}} else {{
|
||||||
{sad_case}
|
{sad_case}
|
||||||
}}
|
}}
|
||||||
}}");
|
}}"
|
||||||
|
);
|
||||||
|
|
||||||
add_method_to_adt(builder, &parent_enum, impl_def, &method);
|
add_method_to_adt(builder, &parent_enum, impl_def, &method);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use ast::make;
|
use ast::make;
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use hir::{db::HirDatabase, PathResolution, Semantics, TypeInfo};
|
use hir::{db::HirDatabase, PathResolution, Semantics, TypeInfo};
|
||||||
|
@ -190,10 +192,10 @@ pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
|
||||||
PathResolution::Def(hir::ModuleDef::Function(f)) => f,
|
PathResolution::Def(hir::ModuleDef::Function(f)) => f,
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
(function, format!("Inline `{}`", path))
|
(function, format!("Inline `{path}`"))
|
||||||
}
|
}
|
||||||
ast::CallableExpr::MethodCall(call) => {
|
ast::CallableExpr::MethodCall(call) => {
|
||||||
(ctx.sema.resolve_method_call(call)?, format!("Inline `{}`", name_ref))
|
(ctx.sema.resolve_method_call(call)?, format!("Inline `{name_ref}`"))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -373,8 +375,44 @@ fn inline(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut func_let_vars: BTreeSet<String> = BTreeSet::new();
|
||||||
|
|
||||||
|
// grab all of the local variable declarations in the function
|
||||||
|
for stmt in fn_body.statements() {
|
||||||
|
if let Some(let_stmt) = ast::LetStmt::cast(stmt.syntax().to_owned()) {
|
||||||
|
for has_token in let_stmt.syntax().children_with_tokens() {
|
||||||
|
if let Some(node) = has_token.as_node() {
|
||||||
|
if let Some(ident_pat) = ast::IdentPat::cast(node.to_owned()) {
|
||||||
|
func_let_vars.insert(ident_pat.syntax().text().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Inline parameter expressions or generate `let` statements depending on whether inlining works or not.
|
// Inline parameter expressions or generate `let` statements depending on whether inlining works or not.
|
||||||
for ((pat, param_ty, _), usages, expr) in izip!(params, param_use_nodes, arguments).rev() {
|
for ((pat, param_ty, _), usages, expr) in izip!(params, param_use_nodes, arguments).rev() {
|
||||||
|
// izip confuses RA due to our lack of hygiene info currently losing us type info causing incorrect errors
|
||||||
|
let usages: &[ast::PathExpr] = &*usages;
|
||||||
|
let expr: &ast::Expr = expr;
|
||||||
|
|
||||||
|
let insert_let_stmt = || {
|
||||||
|
let ty = sema.type_of_expr(expr).filter(TypeInfo::has_adjustment).and(param_ty.clone());
|
||||||
|
if let Some(stmt_list) = body.stmt_list() {
|
||||||
|
stmt_list.push_front(
|
||||||
|
make::let_stmt(pat.clone(), ty, Some(expr.clone())).clone_for_update().into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// check if there is a local var in the function that conflicts with parameter
|
||||||
|
// if it does then emit a let statement and continue
|
||||||
|
if func_let_vars.contains(&expr.syntax().text().to_string()) {
|
||||||
|
insert_let_stmt();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let inline_direct = |usage, replacement: &ast::Expr| {
|
let inline_direct = |usage, replacement: &ast::Expr| {
|
||||||
if let Some(field) = path_expr_as_record_field(usage) {
|
if let Some(field) = path_expr_as_record_field(usage) {
|
||||||
cov_mark::hit!(inline_call_inline_direct_field);
|
cov_mark::hit!(inline_call_inline_direct_field);
|
||||||
|
@ -383,9 +421,7 @@ fn inline(
|
||||||
ted::replace(usage.syntax(), &replacement.syntax().clone_for_update());
|
ted::replace(usage.syntax(), &replacement.syntax().clone_for_update());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// izip confuses RA due to our lack of hygiene info currently losing us type info causing incorrect errors
|
|
||||||
let usages: &[ast::PathExpr] = &*usages;
|
|
||||||
let expr: &ast::Expr = expr;
|
|
||||||
match usages {
|
match usages {
|
||||||
// inline single use closure arguments
|
// inline single use closure arguments
|
||||||
[usage]
|
[usage]
|
||||||
|
@ -408,18 +444,11 @@ fn inline(
|
||||||
}
|
}
|
||||||
// can't inline, emit a let statement
|
// can't inline, emit a let statement
|
||||||
_ => {
|
_ => {
|
||||||
let ty =
|
insert_let_stmt();
|
||||||
sema.type_of_expr(expr).filter(TypeInfo::has_adjustment).and(param_ty.clone());
|
|
||||||
if let Some(stmt_list) = body.stmt_list() {
|
|
||||||
stmt_list.push_front(
|
|
||||||
make::let_stmt(pat.clone(), ty, Some(expr.clone()))
|
|
||||||
.clone_for_update()
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()))
|
||||||
{
|
{
|
||||||
|
@ -1256,4 +1285,37 @@ impl A {
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn local_variable_shadowing_callers_argument() {
|
||||||
|
check_assist(
|
||||||
|
inline_call,
|
||||||
|
r#"
|
||||||
|
fn foo(bar: u32, baz: u32) -> u32 {
|
||||||
|
let a = 1;
|
||||||
|
bar * baz * a * 6
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
let a = 7;
|
||||||
|
let b = 1;
|
||||||
|
let res = foo$0(a, b);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
fn foo(bar: u32, baz: u32) -> u32 {
|
||||||
|
let a = 1;
|
||||||
|
bar * baz * a * 6
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
let a = 7;
|
||||||
|
let b = 1;
|
||||||
|
let res = {
|
||||||
|
let bar = a;
|
||||||
|
let a = 1;
|
||||||
|
bar * b * a * 6
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,7 +113,7 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||||
.collect::<Option<Vec<_>>>()?;
|
.collect::<Option<Vec<_>>>()?;
|
||||||
|
|
||||||
let init_str = initializer_expr.syntax().text().to_string();
|
let init_str = initializer_expr.syntax().text().to_string();
|
||||||
let init_in_paren = format!("({})", &init_str);
|
let init_in_paren = format!("({init_str})");
|
||||||
|
|
||||||
let target = match target {
|
let target = match target {
|
||||||
ast::NameOrNameRef::Name(it) => it.syntax().text_range(),
|
ast::NameOrNameRef::Name(it) => it.syntax().text_range(),
|
||||||
|
@ -132,7 +132,7 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||||
let replacement = if should_wrap { &init_in_paren } else { &init_str };
|
let replacement = if should_wrap { &init_in_paren } else { &init_str };
|
||||||
if ast::RecordExprField::for_field_name(&name).is_some() {
|
if ast::RecordExprField::for_field_name(&name).is_some() {
|
||||||
cov_mark::hit!(inline_field_shorthand);
|
cov_mark::hit!(inline_field_shorthand);
|
||||||
builder.insert(range.end(), format!(": {}", replacement));
|
builder.insert(range.end(), format!(": {replacement}"));
|
||||||
} else {
|
} else {
|
||||||
builder.replace(range, replacement.clone())
|
builder.replace(range, replacement.clone())
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,7 +127,7 @@ fn generate_unique_lifetime_param_name(
|
||||||
Some(type_params) => {
|
Some(type_params) => {
|
||||||
let used_lifetime_params: FxHashSet<_> =
|
let used_lifetime_params: FxHashSet<_> =
|
||||||
type_params.lifetime_params().map(|p| p.syntax().text().to_string()).collect();
|
type_params.lifetime_params().map(|p| p.syntax().text().to_string()).collect();
|
||||||
('a'..='z').map(|it| format!("'{}", it)).find(|it| !used_lifetime_params.contains(it))
|
('a'..='z').map(|it| format!("'{it}")).find(|it| !used_lifetime_params.contains(it))
|
||||||
}
|
}
|
||||||
None => Some("'a".to_string()),
|
None => Some("'a".to_string()),
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@ pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
|
||||||
.join(" | ")
|
.join(" | ")
|
||||||
};
|
};
|
||||||
|
|
||||||
let arm = format!("{} => {},", pats, current_expr.syntax().text());
|
let arm = format!("{pats} => {current_expr},");
|
||||||
|
|
||||||
if let [first, .., last] = &*arms_to_merge {
|
if let [first, .., last] = &*arms_to_merge {
|
||||||
let start = first.syntax().text_range().start();
|
let start = first.syntax().text_range().start();
|
||||||
|
|
|
@ -40,11 +40,11 @@ pub(crate) fn move_from_mod_rs(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
|
||||||
|
|
||||||
let target = source_file.syntax().text_range();
|
let target = source_file.syntax().text_range();
|
||||||
let module_name = module.name(ctx.db())?.to_string();
|
let module_name = module.name(ctx.db())?.to_string();
|
||||||
let path = format!("../{}.rs", module_name);
|
let path = format!("../{module_name}.rs");
|
||||||
let dst = AnchoredPathBuf { anchor: ctx.file_id(), path };
|
let dst = AnchoredPathBuf { anchor: ctx.file_id(), path };
|
||||||
acc.add(
|
acc.add(
|
||||||
AssistId("move_from_mod_rs", AssistKind::Refactor),
|
AssistId("move_from_mod_rs", AssistKind::Refactor),
|
||||||
format!("Convert {}/mod.rs to {}.rs", module_name, module_name),
|
format!("Convert {module_name}/mod.rs to {module_name}.rs"),
|
||||||
target,
|
target,
|
||||||
|builder| {
|
|builder| {
|
||||||
builder.move_file(ctx.file_id(), dst);
|
builder.move_file(ctx.file_id(), dst);
|
||||||
|
|
|
@ -133,16 +133,16 @@ pub(crate) fn move_arm_cond_to_match_guard(
|
||||||
};
|
};
|
||||||
let then_arm_end = match_arm.syntax().text_range().end();
|
let then_arm_end = match_arm.syntax().text_range().end();
|
||||||
let indent_level = match_arm.indent_level();
|
let indent_level = match_arm.indent_level();
|
||||||
let spaces = " ".repeat(indent_level.0 as _);
|
let spaces = indent_level;
|
||||||
|
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
for (cond, block) in conds_blocks {
|
for (cond, block) in conds_blocks {
|
||||||
if !first {
|
if !first {
|
||||||
edit.insert(then_arm_end, format!("\n{}", spaces));
|
edit.insert(then_arm_end, format!("\n{spaces}"));
|
||||||
} else {
|
} else {
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
let guard = format!("{} if {} => ", match_pat, cond.syntax().text());
|
let guard = format!("{match_pat} if {cond} => ");
|
||||||
edit.insert(then_arm_end, guard);
|
edit.insert(then_arm_end, guard);
|
||||||
let only_expr = block.statements().next().is_none();
|
let only_expr = block.statements().next().is_none();
|
||||||
match &block.tail_expr() {
|
match &block.tail_expr() {
|
||||||
|
@ -158,7 +158,7 @@ pub(crate) fn move_arm_cond_to_match_guard(
|
||||||
}
|
}
|
||||||
if let Some(e) = tail {
|
if let Some(e) = tail {
|
||||||
cov_mark::hit!(move_guard_ifelse_else_tail);
|
cov_mark::hit!(move_guard_ifelse_else_tail);
|
||||||
let guard = format!("\n{}{} => ", spaces, match_pat);
|
let guard = format!("\n{spaces}{match_pat} => ");
|
||||||
edit.insert(then_arm_end, guard);
|
edit.insert(then_arm_end, guard);
|
||||||
let only_expr = e.statements().next().is_none();
|
let only_expr = e.statements().next().is_none();
|
||||||
match &e.tail_expr() {
|
match &e.tail_expr() {
|
||||||
|
@ -183,7 +183,7 @@ pub(crate) fn move_arm_cond_to_match_guard(
|
||||||
{
|
{
|
||||||
cov_mark::hit!(move_guard_ifelse_has_wildcard);
|
cov_mark::hit!(move_guard_ifelse_has_wildcard);
|
||||||
}
|
}
|
||||||
_ => edit.insert(then_arm_end, format!("\n{}{} => {{}}", spaces, match_pat)),
|
_ => edit.insert(then_arm_end, format!("\n{spaces}{match_pat} => {{}}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -52,7 +52,7 @@ pub(crate) fn move_module_to_file(acc: &mut Assists, ctx: &AssistContext<'_>) ->
|
||||||
let mut buf = String::from("./");
|
let mut buf = String::from("./");
|
||||||
match parent_module.name(ctx.db()) {
|
match parent_module.name(ctx.db()) {
|
||||||
Some(name) if !parent_module.is_mod_rs(ctx.db()) => {
|
Some(name) if !parent_module.is_mod_rs(ctx.db()) => {
|
||||||
format_to!(buf, "{}/", name)
|
format_to!(buf, "{name}/")
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ pub(crate) fn move_module_to_file(acc: &mut Assists, ctx: &AssistContext<'_>) ->
|
||||||
items
|
items
|
||||||
};
|
};
|
||||||
|
|
||||||
let buf = format!("mod {};", module_name);
|
let buf = format!("mod {module_name};");
|
||||||
|
|
||||||
let replacement_start = match module_ast.mod_token() {
|
let replacement_start = match module_ast.mod_token() {
|
||||||
Some(mod_token) => mod_token.text_range(),
|
Some(mod_token) => mod_token.text_range(),
|
||||||
|
|
|
@ -40,11 +40,11 @@ pub(crate) fn move_to_mod_rs(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
|
||||||
|
|
||||||
let target = source_file.syntax().text_range();
|
let target = source_file.syntax().text_range();
|
||||||
let module_name = module.name(ctx.db())?.to_string();
|
let module_name = module.name(ctx.db())?.to_string();
|
||||||
let path = format!("./{}/mod.rs", module_name);
|
let path = format!("./{module_name}/mod.rs");
|
||||||
let dst = AnchoredPathBuf { anchor: ctx.file_id(), path };
|
let dst = AnchoredPathBuf { anchor: ctx.file_id(), path };
|
||||||
acc.add(
|
acc.add(
|
||||||
AssistId("move_to_mod_rs", AssistKind::Refactor),
|
AssistId("move_to_mod_rs", AssistKind::Refactor),
|
||||||
format!("Convert {}.rs to {}/mod.rs", module_name, module_name),
|
format!("Convert {module_name}.rs to {module_name}/mod.rs"),
|
||||||
target,
|
target,
|
||||||
|builder| {
|
|builder| {
|
||||||
builder.move_file(ctx.file_id(), dst);
|
builder.move_file(ctx.file_id(), dst);
|
||||||
|
|
|
@ -38,7 +38,7 @@ pub(crate) fn reformat_number_literal(acc: &mut Assists, ctx: &AssistContext<'_>
|
||||||
converted.push_str(suffix);
|
converted.push_str(suffix);
|
||||||
|
|
||||||
let group_id = GroupLabel("Reformat number literal".into());
|
let group_id = GroupLabel("Reformat number literal".into());
|
||||||
let label = format!("Convert {} to {}", literal, converted);
|
let label = format!("Convert {literal} to {converted}");
|
||||||
let range = literal.syntax().text_range();
|
let range = literal.syntax().text_range();
|
||||||
acc.add_group(
|
acc.add_group(
|
||||||
&group_id,
|
&group_id,
|
||||||
|
|
|
@ -54,7 +54,7 @@ pub(crate) fn qualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) ->
|
||||||
|
|
||||||
acc.add(
|
acc.add(
|
||||||
AssistId("qualify_method_call", AssistKind::RefactorInline),
|
AssistId("qualify_method_call", AssistKind::RefactorInline),
|
||||||
format!("Qualify `{}` method call", ident.text()),
|
format!("Qualify `{ident}` method call"),
|
||||||
range,
|
range,
|
||||||
|builder| {
|
|builder| {
|
||||||
qualify_candidate.qualify(
|
qualify_candidate.qualify(
|
||||||
|
|
|
@ -118,14 +118,14 @@ impl QualifyCandidate<'_> {
|
||||||
match self {
|
match self {
|
||||||
QualifyCandidate::QualifierStart(segment, generics) => {
|
QualifyCandidate::QualifierStart(segment, generics) => {
|
||||||
let generics = generics.as_ref().map_or_else(String::new, ToString::to_string);
|
let generics = generics.as_ref().map_or_else(String::new, ToString::to_string);
|
||||||
replacer(format!("{}{}::{}", import, generics, segment));
|
replacer(format!("{import}{generics}::{segment}"));
|
||||||
}
|
}
|
||||||
QualifyCandidate::UnqualifiedName(generics) => {
|
QualifyCandidate::UnqualifiedName(generics) => {
|
||||||
let generics = generics.as_ref().map_or_else(String::new, ToString::to_string);
|
let generics = generics.as_ref().map_or_else(String::new, ToString::to_string);
|
||||||
replacer(format!("{}{}", import, generics));
|
replacer(format!("{import}{generics}"));
|
||||||
}
|
}
|
||||||
QualifyCandidate::TraitAssocItem(qualifier, segment) => {
|
QualifyCandidate::TraitAssocItem(qualifier, segment) => {
|
||||||
replacer(format!("<{} as {}>::{}", qualifier, import, segment));
|
replacer(format!("<{qualifier} as {import}>::{segment}"));
|
||||||
}
|
}
|
||||||
QualifyCandidate::TraitMethod(db, mcall_expr) => {
|
QualifyCandidate::TraitMethod(db, mcall_expr) => {
|
||||||
Self::qualify_trait_method(db, mcall_expr, replacer, import, item);
|
Self::qualify_trait_method(db, mcall_expr, replacer, import, item);
|
||||||
|
@ -155,16 +155,11 @@ impl QualifyCandidate<'_> {
|
||||||
hir::Access::Exclusive => make::expr_ref(receiver, true),
|
hir::Access::Exclusive => make::expr_ref(receiver, true),
|
||||||
hir::Access::Owned => receiver,
|
hir::Access::Owned => receiver,
|
||||||
};
|
};
|
||||||
replacer(format!(
|
let arg_list = match arg_list {
|
||||||
"{}::{}{}{}",
|
|
||||||
import,
|
|
||||||
method_name,
|
|
||||||
generics,
|
|
||||||
match arg_list {
|
|
||||||
Some(args) => make::arg_list(iter::once(receiver).chain(args)),
|
Some(args) => make::arg_list(iter::once(receiver).chain(args)),
|
||||||
None => make::arg_list(iter::once(receiver)),
|
None => make::arg_list(iter::once(receiver)),
|
||||||
}
|
};
|
||||||
));
|
replacer(format!("{import}::{method_name}{generics}{arg_list}"));
|
||||||
}
|
}
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
@ -218,15 +213,17 @@ fn group_label(candidate: &ImportCandidate) -> GroupLabel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.text();
|
.text();
|
||||||
GroupLabel(format!("Qualify {}", name))
|
GroupLabel(format!("Qualify {name}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn label(candidate: &ImportCandidate, import: &LocatedImport) -> String {
|
fn label(candidate: &ImportCandidate, import: &LocatedImport) -> String {
|
||||||
|
let import_path = &import.import_path;
|
||||||
|
|
||||||
match candidate {
|
match candidate {
|
||||||
ImportCandidate::Path(candidate) if candidate.qualifier.is_none() => {
|
ImportCandidate::Path(candidate) if candidate.qualifier.is_none() => {
|
||||||
format!("Qualify as `{}`", import.import_path)
|
format!("Qualify as `{import_path}`")
|
||||||
}
|
}
|
||||||
_ => format!("Qualify with `{}`", import.import_path),
|
_ => format!("Qualify with `{import_path}`"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,13 +34,10 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt
|
||||||
let hashes = "#".repeat(required_hashes(&value).max(1));
|
let hashes = "#".repeat(required_hashes(&value).max(1));
|
||||||
if matches!(value, Cow::Borrowed(_)) {
|
if matches!(value, Cow::Borrowed(_)) {
|
||||||
// Avoid replacing the whole string to better position the cursor.
|
// Avoid replacing the whole string to better position the cursor.
|
||||||
edit.insert(token.syntax().text_range().start(), format!("r{}", hashes));
|
edit.insert(token.syntax().text_range().start(), format!("r{hashes}"));
|
||||||
edit.insert(token.syntax().text_range().end(), hashes);
|
edit.insert(token.syntax().text_range().end(), hashes);
|
||||||
} else {
|
} else {
|
||||||
edit.replace(
|
edit.replace(token.syntax().text_range(), format!("r{hashes}\"{value}\"{hashes}"));
|
||||||
token.syntax().text_range(),
|
|
||||||
format!("r{}\"{}\"{}", hashes, value, hashes),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -83,7 +80,7 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
|
edit.replace(token.syntax().text_range(), format!("\"{escaped}\""));
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<(
|
||||||
};
|
};
|
||||||
(
|
(
|
||||||
macro_call.syntax().text_range(),
|
macro_call.syntax().text_range(),
|
||||||
if wrap { format!("({})", expr) } else { expr.to_string() },
|
if wrap { format!("({expr})") } else { expr.to_string() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// dbg!(expr0, expr1, ...)
|
// dbg!(expr0, expr1, ...)
|
||||||
|
@ -127,8 +127,8 @@ mod tests {
|
||||||
fn check(ra_fixture_before: &str, ra_fixture_after: &str) {
|
fn check(ra_fixture_before: &str, ra_fixture_after: &str) {
|
||||||
check_assist(
|
check_assist(
|
||||||
remove_dbg,
|
remove_dbg,
|
||||||
&format!("fn main() {{\n{}\n}}", ra_fixture_before),
|
&format!("fn main() {{\n{ra_fixture_before}\n}}"),
|
||||||
&format!("fn main() {{\n{}\n}}", ra_fixture_after),
|
&format!("fn main() {{\n{ra_fixture_after}\n}}"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -124,7 +124,7 @@ fn add_assist(
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let target = attr.syntax().text_range();
|
let target = attr.syntax().text_range();
|
||||||
let annotated_name = adt.name()?;
|
let annotated_name = adt.name()?;
|
||||||
let label = format!("Convert to manual `impl {} for {}`", replace_trait_path, annotated_name);
|
let label = format!("Convert to manual `impl {replace_trait_path} for {annotated_name}`");
|
||||||
|
|
||||||
acc.add(
|
acc.add(
|
||||||
AssistId("replace_derive_with_manual_impl", AssistKind::Refactor),
|
AssistId("replace_derive_with_manual_impl", AssistKind::Refactor),
|
||||||
|
@ -158,11 +158,8 @@ fn add_assist(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.insert_snippet(
|
let rendered = render_snippet(cap, impl_def.syntax(), cursor);
|
||||||
cap,
|
builder.insert_snippet(cap, insert_pos, format!("\n\n{rendered}"))
|
||||||
insert_pos,
|
|
||||||
format!("\n\n{}", render_snippet(cap, impl_def.syntax(), cursor)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -62,7 +62,7 @@ pub(crate) fn replace_or_with_or_else(acc: &mut Assists, ctx: &AssistContext<'_>
|
||||||
|
|
||||||
acc.add(
|
acc.add(
|
||||||
AssistId("replace_or_with_or_else", AssistKind::RefactorRewrite),
|
AssistId("replace_or_with_or_else", AssistKind::RefactorRewrite),
|
||||||
format!("Replace {} with {}", name.text(), replace),
|
format!("Replace {name} with {replace}"),
|
||||||
call.syntax().text_range(),
|
call.syntax().text_range(),
|
||||||
|builder| {
|
|builder| {
|
||||||
builder.replace(name.syntax().text_range(), replace);
|
builder.replace(name.syntax().text_range(), replace);
|
||||||
|
@ -138,7 +138,7 @@ pub(crate) fn replace_or_else_with_or(acc: &mut Assists, ctx: &AssistContext<'_>
|
||||||
|
|
||||||
acc.add(
|
acc.add(
|
||||||
AssistId("replace_or_else_with_or", AssistKind::RefactorRewrite),
|
AssistId("replace_or_else_with_or", AssistKind::RefactorRewrite),
|
||||||
format!("Replace {} with {}", name.text(), replace),
|
format!("Replace {name} with {replace}"),
|
||||||
call.syntax().text_range(),
|
call.syntax().text_range(),
|
||||||
|builder| {
|
|builder| {
|
||||||
builder.replace(name.syntax().text_range(), replace);
|
builder.replace(name.syntax().text_range(), replace);
|
||||||
|
|
|
@ -79,7 +79,7 @@ pub(crate) fn replace_turbofish_with_explicit_type(
|
||||||
"Replace turbofish with explicit type",
|
"Replace turbofish with explicit type",
|
||||||
TextRange::new(initializer_start, turbofish_range.end()),
|
TextRange::new(initializer_start, turbofish_range.end()),
|
||||||
|builder| {
|
|builder| {
|
||||||
builder.insert(ident_range.end(), format!(": {}", returned_type));
|
builder.insert(ident_range.end(), format!(": {returned_type}"));
|
||||||
builder.delete(turbofish_range);
|
builder.delete(turbofish_range);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -44,6 +44,12 @@ pub(crate) fn unnecessary_async(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
|
||||||
if function.body()?.syntax().descendants().find_map(ast::AwaitExpr::cast).is_some() {
|
if function.body()?.syntax().descendants().find_map(ast::AwaitExpr::cast).is_some() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
// Do nothing if the method is a member of trait.
|
||||||
|
if let Some(impl_) = function.syntax().ancestors().nth(2).and_then(ast::Impl::cast) {
|
||||||
|
if let Some(_) = impl_.trait_() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the `async` keyword plus whitespace after it, if any.
|
// Remove the `async` keyword plus whitespace after it, if any.
|
||||||
let async_range = {
|
let async_range = {
|
||||||
|
@ -254,4 +260,18 @@ pub async fn f(s: &S) { s.f2() }"#,
|
||||||
fn does_not_apply_when_not_on_prototype() {
|
fn does_not_apply_when_not_on_prototype() {
|
||||||
check_assist_not_applicable(unnecessary_async, "pub async fn f() { $0f2() }")
|
check_assist_not_applicable(unnecessary_async, "pub async fn f() { $0f2() }")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn does_not_apply_on_async_trait_method() {
|
||||||
|
check_assist_not_applicable(
|
||||||
|
unnecessary_async,
|
||||||
|
r#"
|
||||||
|
trait Trait {
|
||||||
|
async fn foo();
|
||||||
|
}
|
||||||
|
impl Trait for () {
|
||||||
|
$0async fn foo() {}
|
||||||
|
}"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,13 +69,13 @@ pub(crate) fn unwrap_tuple(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
|
||||||
for (pat, ty, expr) in
|
for (pat, ty, expr) in
|
||||||
itertools::izip!(tuple_pat.fields(), tys.fields(), tuple_init.fields())
|
itertools::izip!(tuple_pat.fields(), tys.fields(), tuple_init.fields())
|
||||||
{
|
{
|
||||||
zipped_decls.push_str(&format!("{}let {pat}: {ty} = {expr};\n", indents))
|
zipped_decls.push_str(&format!("{indents}let {pat}: {ty} = {expr};\n"))
|
||||||
}
|
}
|
||||||
edit.replace(parent.text_range(), zipped_decls.trim());
|
edit.replace(parent.text_range(), zipped_decls.trim());
|
||||||
} else {
|
} else {
|
||||||
let mut zipped_decls = String::new();
|
let mut zipped_decls = String::new();
|
||||||
for (pat, expr) in itertools::izip!(tuple_pat.fields(), tuple_init.fields()) {
|
for (pat, expr) in itertools::izip!(tuple_pat.fields(), tuple_init.fields()) {
|
||||||
zipped_decls.push_str(&format!("{}let {pat} = {expr};\n", indents));
|
zipped_decls.push_str(&format!("{indents}let {pat} = {expr};\n"));
|
||||||
}
|
}
|
||||||
edit.replace(parent.text_range(), zipped_decls.trim());
|
edit.replace(parent.text_range(), zipped_decls.trim());
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,11 +76,11 @@ pub(crate) fn wrap_return_type_in_result(acc: &mut Assists, ctx: &AssistContext<
|
||||||
|
|
||||||
match ctx.config.snippet_cap {
|
match ctx.config.snippet_cap {
|
||||||
Some(cap) => {
|
Some(cap) => {
|
||||||
let snippet = format!("Result<{}, ${{0:_}}>", type_ref);
|
let snippet = format!("Result<{type_ref}, ${{0:_}}>");
|
||||||
builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet)
|
builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet)
|
||||||
}
|
}
|
||||||
None => builder
|
None => builder
|
||||||
.replace(type_ref.syntax().text_range(), format!("Result<{}, _>", type_ref)),
|
.replace(type_ref.syntax().text_range(), format!("Result<{type_ref}, _>")),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -120,6 +120,7 @@ mod handlers {
|
||||||
mod convert_into_to_from;
|
mod convert_into_to_from;
|
||||||
mod convert_iter_for_each_to_for;
|
mod convert_iter_for_each_to_for;
|
||||||
mod convert_let_else_to_match;
|
mod convert_let_else_to_match;
|
||||||
|
mod convert_match_to_let_else;
|
||||||
mod convert_tuple_struct_to_named_struct;
|
mod convert_tuple_struct_to_named_struct;
|
||||||
mod convert_named_struct_to_tuple_struct;
|
mod convert_named_struct_to_tuple_struct;
|
||||||
mod convert_to_guarded_return;
|
mod convert_to_guarded_return;
|
||||||
|
@ -220,6 +221,7 @@ mod handlers {
|
||||||
convert_iter_for_each_to_for::convert_for_loop_with_for_each,
|
convert_iter_for_each_to_for::convert_for_loop_with_for_each,
|
||||||
convert_let_else_to_match::convert_let_else_to_match,
|
convert_let_else_to_match::convert_let_else_to_match,
|
||||||
convert_named_struct_to_tuple_struct::convert_named_struct_to_tuple_struct,
|
convert_named_struct_to_tuple_struct::convert_named_struct_to_tuple_struct,
|
||||||
|
convert_match_to_let_else::convert_match_to_let_else,
|
||||||
convert_to_guarded_return::convert_to_guarded_return,
|
convert_to_guarded_return::convert_to_guarded_return,
|
||||||
convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct,
|
convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct,
|
||||||
convert_two_arm_bool_match_to_matches_macro::convert_two_arm_bool_match_to_matches_macro,
|
convert_two_arm_bool_match_to_matches_macro::convert_two_arm_bool_match_to_matches_macro,
|
||||||
|
|
|
@ -30,6 +30,7 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
|
||||||
skip_glob_imports: true,
|
skip_glob_imports: true,
|
||||||
},
|
},
|
||||||
prefer_no_std: false,
|
prefer_no_std: false,
|
||||||
|
assist_emit_must_use: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
|
pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
|
||||||
|
|
|
@ -407,6 +407,27 @@ fn main() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn doctest_convert_match_to_let_else() {
|
||||||
|
check_doc_test(
|
||||||
|
"convert_match_to_let_else",
|
||||||
|
r#####"
|
||||||
|
//- minicore: option
|
||||||
|
fn foo(opt: Option<()>) {
|
||||||
|
let val = $0match opt {
|
||||||
|
Some(it) => it,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#####,
|
||||||
|
r#####"
|
||||||
|
fn foo(opt: Option<()>) {
|
||||||
|
let Some(val) = opt else { return };
|
||||||
|
}
|
||||||
|
"#####,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn doctest_convert_named_struct_to_tuple_struct() {
|
fn doctest_convert_named_struct_to_tuple_struct() {
|
||||||
check_doc_test(
|
check_doc_test(
|
||||||
|
|
|
@ -189,8 +189,8 @@ pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor
|
||||||
let mut placeholder = cursor.node().to_string();
|
let mut placeholder = cursor.node().to_string();
|
||||||
escape(&mut placeholder);
|
escape(&mut placeholder);
|
||||||
let tab_stop = match cursor {
|
let tab_stop = match cursor {
|
||||||
Cursor::Replace(placeholder) => format!("${{0:{}}}", placeholder),
|
Cursor::Replace(placeholder) => format!("${{0:{placeholder}}}"),
|
||||||
Cursor::Before(placeholder) => format!("$0{}", placeholder),
|
Cursor::Before(placeholder) => format!("$0{placeholder}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut buf = node.to_string();
|
let mut buf = node.to_string();
|
||||||
|
@ -539,17 +539,17 @@ impl ReferenceConversion {
|
||||||
ReferenceConversionType::AsRefSlice => {
|
ReferenceConversionType::AsRefSlice => {
|
||||||
let type_argument_name =
|
let type_argument_name =
|
||||||
self.ty.type_arguments().next().unwrap().display(db).to_string();
|
self.ty.type_arguments().next().unwrap().display(db).to_string();
|
||||||
format!("&[{}]", type_argument_name)
|
format!("&[{type_argument_name}]")
|
||||||
}
|
}
|
||||||
ReferenceConversionType::Dereferenced => {
|
ReferenceConversionType::Dereferenced => {
|
||||||
let type_argument_name =
|
let type_argument_name =
|
||||||
self.ty.type_arguments().next().unwrap().display(db).to_string();
|
self.ty.type_arguments().next().unwrap().display(db).to_string();
|
||||||
format!("&{}", type_argument_name)
|
format!("&{type_argument_name}")
|
||||||
}
|
}
|
||||||
ReferenceConversionType::Option => {
|
ReferenceConversionType::Option => {
|
||||||
let type_argument_name =
|
let type_argument_name =
|
||||||
self.ty.type_arguments().next().unwrap().display(db).to_string();
|
self.ty.type_arguments().next().unwrap().display(db).to_string();
|
||||||
format!("Option<&{}>", type_argument_name)
|
format!("Option<&{type_argument_name}>")
|
||||||
}
|
}
|
||||||
ReferenceConversionType::Result => {
|
ReferenceConversionType::Result => {
|
||||||
let mut type_arguments = self.ty.type_arguments();
|
let mut type_arguments = self.ty.type_arguments();
|
||||||
|
@ -557,19 +557,19 @@ impl ReferenceConversion {
|
||||||
type_arguments.next().unwrap().display(db).to_string();
|
type_arguments.next().unwrap().display(db).to_string();
|
||||||
let second_type_argument_name =
|
let second_type_argument_name =
|
||||||
type_arguments.next().unwrap().display(db).to_string();
|
type_arguments.next().unwrap().display(db).to_string();
|
||||||
format!("Result<&{}, &{}>", first_type_argument_name, second_type_argument_name)
|
format!("Result<&{first_type_argument_name}, &{second_type_argument_name}>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn getter(&self, field_name: String) -> String {
|
pub(crate) fn getter(&self, field_name: String) -> String {
|
||||||
match self.conversion {
|
match self.conversion {
|
||||||
ReferenceConversionType::Copy => format!("self.{}", field_name),
|
ReferenceConversionType::Copy => format!("self.{field_name}"),
|
||||||
ReferenceConversionType::AsRefStr
|
ReferenceConversionType::AsRefStr
|
||||||
| ReferenceConversionType::AsRefSlice
|
| ReferenceConversionType::AsRefSlice
|
||||||
| ReferenceConversionType::Dereferenced
|
| ReferenceConversionType::Dereferenced
|
||||||
| ReferenceConversionType::Option
|
| ReferenceConversionType::Option
|
||||||
| ReferenceConversionType::Result => format!("self.{}.as_ref()", field_name),
|
| ReferenceConversionType::Result => format!("self.{field_name}.as_ref()"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ fn gen_clone_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
|
||||||
let mut arms = vec![];
|
let mut arms = vec![];
|
||||||
for variant in list.variants() {
|
for variant in list.variants() {
|
||||||
let name = variant.name()?;
|
let name = variant.name()?;
|
||||||
let variant_name = make::ext::path_from_idents(["Self", &format!("{}", name)])?;
|
let variant_name = make::ext::path_from_idents(["Self", &format!("{name}")])?;
|
||||||
|
|
||||||
match variant.field_list() {
|
match variant.field_list() {
|
||||||
// => match self { Self::Name { x } => Self::Name { x: x.clone() } }
|
// => match self { Self::Name { x } => Self::Name { x: x.clone() } }
|
||||||
|
@ -70,7 +70,7 @@ fn gen_clone_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
|
||||||
let mut pats = vec![];
|
let mut pats = vec![];
|
||||||
let mut fields = vec![];
|
let mut fields = vec![];
|
||||||
for (i, _) in list.fields().enumerate() {
|
for (i, _) in list.fields().enumerate() {
|
||||||
let field_name = format!("arg{}", i);
|
let field_name = format!("arg{i}");
|
||||||
let pat = make::ident_pat(false, false, make::name(&field_name));
|
let pat = make::ident_pat(false, false, make::name(&field_name));
|
||||||
pats.push(pat.into());
|
pats.push(pat.into());
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ fn gen_clone_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
|
||||||
let mut fields = vec![];
|
let mut fields = vec![];
|
||||||
for (i, _) in field_list.fields().enumerate() {
|
for (i, _) in field_list.fields().enumerate() {
|
||||||
let f_path = make::expr_path(make::ext::ident_path("self"));
|
let f_path = make::expr_path(make::ext::ident_path("self"));
|
||||||
let target = make::expr_field(f_path, &format!("{}", i));
|
let target = make::expr_field(f_path, &format!("{i}"));
|
||||||
fields.push(gen_clone_call(target));
|
fields.push(gen_clone_call(target));
|
||||||
}
|
}
|
||||||
let struct_name = make::expr_path(make::ext::ident_path("Self"));
|
let struct_name = make::expr_path(make::ext::ident_path("Self"));
|
||||||
|
@ -151,7 +151,7 @@ fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
|
||||||
let mut arms = vec![];
|
let mut arms = vec![];
|
||||||
for variant in list.variants() {
|
for variant in list.variants() {
|
||||||
let name = variant.name()?;
|
let name = variant.name()?;
|
||||||
let variant_name = make::ext::path_from_idents(["Self", &format!("{}", name)])?;
|
let variant_name = make::ext::path_from_idents(["Self", &format!("{name}")])?;
|
||||||
let target = make::expr_path(make::ext::ident_path("f"));
|
let target = make::expr_path(make::ext::ident_path("f"));
|
||||||
|
|
||||||
match variant.field_list() {
|
match variant.field_list() {
|
||||||
|
@ -159,7 +159,7 @@ fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
|
||||||
// => f.debug_struct(name)
|
// => f.debug_struct(name)
|
||||||
let target = make::expr_path(make::ext::ident_path("f"));
|
let target = make::expr_path(make::ext::ident_path("f"));
|
||||||
let method = make::name_ref("debug_struct");
|
let method = make::name_ref("debug_struct");
|
||||||
let struct_name = format!("\"{}\"", name);
|
let struct_name = format!("\"{name}\"");
|
||||||
let args = make::arg_list(Some(make::expr_literal(&struct_name).into()));
|
let args = make::arg_list(Some(make::expr_literal(&struct_name).into()));
|
||||||
let mut expr = make::expr_method_call(target, method, args);
|
let mut expr = make::expr_method_call(target, method, args);
|
||||||
|
|
||||||
|
@ -173,8 +173,8 @@ fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
|
||||||
|
|
||||||
// => <expr>.field("field_name", field)
|
// => <expr>.field("field_name", field)
|
||||||
let method_name = make::name_ref("field");
|
let method_name = make::name_ref("field");
|
||||||
let name = make::expr_literal(&(format!("\"{}\"", field_name))).into();
|
let name = make::expr_literal(&(format!("\"{field_name}\""))).into();
|
||||||
let path = &format!("{}", field_name);
|
let path = &format!("{field_name}");
|
||||||
let path = make::expr_path(make::ext::ident_path(path));
|
let path = make::expr_path(make::ext::ident_path(path));
|
||||||
let args = make::arg_list(vec![name, path]);
|
let args = make::arg_list(vec![name, path]);
|
||||||
expr = make::expr_method_call(expr, method_name, args);
|
expr = make::expr_method_call(expr, method_name, args);
|
||||||
|
@ -192,13 +192,13 @@ fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
|
||||||
// => f.debug_tuple(name)
|
// => f.debug_tuple(name)
|
||||||
let target = make::expr_path(make::ext::ident_path("f"));
|
let target = make::expr_path(make::ext::ident_path("f"));
|
||||||
let method = make::name_ref("debug_tuple");
|
let method = make::name_ref("debug_tuple");
|
||||||
let struct_name = format!("\"{}\"", name);
|
let struct_name = format!("\"{name}\"");
|
||||||
let args = make::arg_list(Some(make::expr_literal(&struct_name).into()));
|
let args = make::arg_list(Some(make::expr_literal(&struct_name).into()));
|
||||||
let mut expr = make::expr_method_call(target, method, args);
|
let mut expr = make::expr_method_call(target, method, args);
|
||||||
|
|
||||||
let mut pats = vec![];
|
let mut pats = vec![];
|
||||||
for (i, _) in list.fields().enumerate() {
|
for (i, _) in list.fields().enumerate() {
|
||||||
let name = format!("arg{}", i);
|
let name = format!("arg{i}");
|
||||||
|
|
||||||
// create a field pattern for use in `MyStruct(fields..)`
|
// create a field pattern for use in `MyStruct(fields..)`
|
||||||
let field_name = make::name(&name);
|
let field_name = make::name(&name);
|
||||||
|
@ -222,7 +222,7 @@ fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
|
||||||
arms.push(make::match_arm(Some(pat.into()), None, expr));
|
arms.push(make::match_arm(Some(pat.into()), None, expr));
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let fmt_string = make::expr_literal(&(format!("\"{}\"", name))).into();
|
let fmt_string = make::expr_literal(&(format!("\"{name}\""))).into();
|
||||||
let args = make::arg_list([target, fmt_string]);
|
let args = make::arg_list([target, fmt_string]);
|
||||||
let macro_name = make::expr_path(make::ext::ident_path("write"));
|
let macro_name = make::expr_path(make::ext::ident_path("write"));
|
||||||
let macro_call = make::expr_macro_call(macro_name, args);
|
let macro_call = make::expr_macro_call(macro_name, args);
|
||||||
|
@ -244,7 +244,7 @@ fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
ast::Adt::Struct(strukt) => {
|
ast::Adt::Struct(strukt) => {
|
||||||
let name = format!("\"{}\"", annotated_name);
|
let name = format!("\"{annotated_name}\"");
|
||||||
let args = make::arg_list(Some(make::expr_literal(&name).into()));
|
let args = make::arg_list(Some(make::expr_literal(&name).into()));
|
||||||
let target = make::expr_path(make::ext::ident_path("f"));
|
let target = make::expr_path(make::ext::ident_path("f"));
|
||||||
|
|
||||||
|
@ -258,10 +258,10 @@ fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
|
||||||
let mut expr = make::expr_method_call(target, method, args);
|
let mut expr = make::expr_method_call(target, method, args);
|
||||||
for field in field_list.fields() {
|
for field in field_list.fields() {
|
||||||
let name = field.name()?;
|
let name = field.name()?;
|
||||||
let f_name = make::expr_literal(&(format!("\"{}\"", name))).into();
|
let f_name = make::expr_literal(&(format!("\"{name}\""))).into();
|
||||||
let f_path = make::expr_path(make::ext::ident_path("self"));
|
let f_path = make::expr_path(make::ext::ident_path("self"));
|
||||||
let f_path = make::expr_ref(f_path, false);
|
let f_path = make::expr_ref(f_path, false);
|
||||||
let f_path = make::expr_field(f_path, &format!("{}", name));
|
let f_path = make::expr_field(f_path, &format!("{name}"));
|
||||||
let args = make::arg_list([f_name, f_path]);
|
let args = make::arg_list([f_name, f_path]);
|
||||||
expr = make::expr_method_call(expr, make::name_ref("field"), args);
|
expr = make::expr_method_call(expr, make::name_ref("field"), args);
|
||||||
}
|
}
|
||||||
|
@ -275,7 +275,7 @@ fn gen_debug_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
|
||||||
for (i, _) in field_list.fields().enumerate() {
|
for (i, _) in field_list.fields().enumerate() {
|
||||||
let f_path = make::expr_path(make::ext::ident_path("self"));
|
let f_path = make::expr_path(make::ext::ident_path("self"));
|
||||||
let f_path = make::expr_ref(f_path, false);
|
let f_path = make::expr_ref(f_path, false);
|
||||||
let f_path = make::expr_field(f_path, &format!("{}", i));
|
let f_path = make::expr_field(f_path, &format!("{i}"));
|
||||||
let method = make::name_ref("field");
|
let method = make::name_ref("field");
|
||||||
expr = make::expr_method_call(expr, method, make::arg_list(Some(f_path)));
|
expr = make::expr_method_call(expr, method, make::arg_list(Some(f_path)));
|
||||||
}
|
}
|
||||||
|
@ -379,7 +379,7 @@ fn gen_hash_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
|
||||||
let mut stmts = vec![];
|
let mut stmts = vec![];
|
||||||
for (i, _) in field_list.fields().enumerate() {
|
for (i, _) in field_list.fields().enumerate() {
|
||||||
let base = make::expr_path(make::ext::ident_path("self"));
|
let base = make::expr_path(make::ext::ident_path("self"));
|
||||||
let target = make::expr_field(base, &format!("{}", i));
|
let target = make::expr_field(base, &format!("{i}"));
|
||||||
stmts.push(gen_hash_call(target));
|
stmts.push(gen_hash_call(target));
|
||||||
}
|
}
|
||||||
make::block_expr(stmts, None).indent(ast::edit::IndentLevel(1))
|
make::block_expr(stmts, None).indent(ast::edit::IndentLevel(1))
|
||||||
|
@ -453,10 +453,10 @@ fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
|
||||||
for field in list.fields() {
|
for field in list.fields() {
|
||||||
let field_name = field.name()?.to_string();
|
let field_name = field.name()?.to_string();
|
||||||
|
|
||||||
let l_name = &format!("l_{}", field_name);
|
let l_name = &format!("l_{field_name}");
|
||||||
l_fields.push(gen_record_pat_field(&field_name, l_name));
|
l_fields.push(gen_record_pat_field(&field_name, l_name));
|
||||||
|
|
||||||
let r_name = &format!("r_{}", field_name);
|
let r_name = &format!("r_{field_name}");
|
||||||
r_fields.push(gen_record_pat_field(&field_name, r_name));
|
r_fields.push(gen_record_pat_field(&field_name, r_name));
|
||||||
|
|
||||||
let lhs = make::expr_path(make::ext::ident_path(l_name));
|
let lhs = make::expr_path(make::ext::ident_path(l_name));
|
||||||
|
@ -484,12 +484,12 @@ fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
|
||||||
let mut r_fields = vec![];
|
let mut r_fields = vec![];
|
||||||
|
|
||||||
for (i, _) in list.fields().enumerate() {
|
for (i, _) in list.fields().enumerate() {
|
||||||
let field_name = format!("{}", i);
|
let field_name = format!("{i}");
|
||||||
|
|
||||||
let l_name = format!("l{}", field_name);
|
let l_name = format!("l{field_name}");
|
||||||
l_fields.push(gen_tuple_field(&l_name));
|
l_fields.push(gen_tuple_field(&l_name));
|
||||||
|
|
||||||
let r_name = format!("r{}", field_name);
|
let r_name = format!("r{field_name}");
|
||||||
r_fields.push(gen_tuple_field(&r_name));
|
r_fields.push(gen_tuple_field(&r_name));
|
||||||
|
|
||||||
let lhs = make::expr_path(make::ext::ident_path(&l_name));
|
let lhs = make::expr_path(make::ext::ident_path(&l_name));
|
||||||
|
@ -548,7 +548,7 @@ fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
|
||||||
Some(ast::FieldList::TupleFieldList(field_list)) => {
|
Some(ast::FieldList::TupleFieldList(field_list)) => {
|
||||||
let mut expr = None;
|
let mut expr = None;
|
||||||
for (i, _) in field_list.fields().enumerate() {
|
for (i, _) in field_list.fields().enumerate() {
|
||||||
let idx = format!("{}", i);
|
let idx = format!("{i}");
|
||||||
let lhs = make::expr_path(make::ext::ident_path("self"));
|
let lhs = make::expr_path(make::ext::ident_path("self"));
|
||||||
let lhs = make::expr_field(lhs, &idx);
|
let lhs = make::expr_field(lhs, &idx);
|
||||||
let rhs = make::expr_path(make::ext::ident_path("other"));
|
let rhs = make::expr_path(make::ext::ident_path("other"));
|
||||||
|
@ -628,7 +628,7 @@ fn gen_partial_ord(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
|
||||||
Some(ast::FieldList::TupleFieldList(field_list)) => {
|
Some(ast::FieldList::TupleFieldList(field_list)) => {
|
||||||
let mut exprs = vec![];
|
let mut exprs = vec![];
|
||||||
for (i, _) in field_list.fields().enumerate() {
|
for (i, _) in field_list.fields().enumerate() {
|
||||||
let idx = format!("{}", i);
|
let idx = format!("{i}");
|
||||||
let lhs = make::expr_path(make::ext::ident_path("self"));
|
let lhs = make::expr_path(make::ext::ident_path("self"));
|
||||||
let lhs = make::expr_field(lhs, &idx);
|
let lhs = make::expr_field(lhs, &idx);
|
||||||
let rhs = make::expr_path(make::ext::ident_path("other"));
|
let rhs = make::expr_path(make::ext::ident_path("other"));
|
||||||
|
|
|
@ -69,10 +69,6 @@ pub(crate) fn complete_postfix(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.config.snippets.is_empty() {
|
|
||||||
add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);
|
|
||||||
}
|
|
||||||
|
|
||||||
let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references());
|
let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references());
|
||||||
if let Some(try_enum) = &try_enum {
|
if let Some(try_enum) = &try_enum {
|
||||||
match try_enum {
|
match try_enum {
|
||||||
|
@ -140,6 +136,10 @@ pub(crate) fn complete_postfix(
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !ctx.config.snippets.is_empty() {
|
||||||
|
add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);
|
||||||
|
}
|
||||||
|
|
||||||
match try_enum {
|
match try_enum {
|
||||||
Some(try_enum) => match try_enum {
|
Some(try_enum) => match try_enum {
|
||||||
TryEnum::Result => {
|
TryEnum::Result => {
|
||||||
|
@ -613,4 +613,25 @@ fn main() {
|
||||||
r#"fn main() { log::error!("{}", 2+2) }"#,
|
r#"fn main() { log::error!("{}", 2+2) }"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn postfix_custom_snippets_completion_for_references() {
|
||||||
|
check_edit_with_config(
|
||||||
|
CompletionConfig {
|
||||||
|
snippets: vec![Snippet::new(
|
||||||
|
&[],
|
||||||
|
&["ok".into()],
|
||||||
|
&["Ok(${receiver})".into()],
|
||||||
|
"",
|
||||||
|
&[],
|
||||||
|
crate::SnippetScope::Expr,
|
||||||
|
)
|
||||||
|
.unwrap()],
|
||||||
|
..TEST_CONFIG
|
||||||
|
},
|
||||||
|
"ok",
|
||||||
|
r#"fn main() { &&42.$0 }"#,
|
||||||
|
r#"fn main() { Ok(&&42) }"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -446,31 +446,44 @@ impl<'a> FindUsages<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let find_nodes = move |name: &str, node: &syntax::SyntaxNode, offset: TextSize| {
|
||||||
|
node.token_at_offset(offset).find(|it| it.text() == name).map(|token| {
|
||||||
// FIXME: There should be optimization potential here
|
// FIXME: There should be optimization potential here
|
||||||
// Currently we try to descend everything we find which
|
// Currently we try to descend everything we find which
|
||||||
// means we call `Semantics::descend_into_macros` on
|
// means we call `Semantics::descend_into_macros` on
|
||||||
// every textual hit. That function is notoriously
|
// every textual hit. That function is notoriously
|
||||||
// expensive even for things that do not get down mapped
|
// expensive even for things that do not get down mapped
|
||||||
// into macros.
|
// into macros.
|
||||||
|
sema.descend_into_macros(token).into_iter().filter_map(|it| it.parent())
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
for (text, file_id, search_range) in scope_files(sema, &search_scope) {
|
for (text, file_id, search_range) in scope_files(sema, &search_scope) {
|
||||||
let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());
|
let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());
|
||||||
|
|
||||||
// Search for occurrences of the items name
|
// Search for occurrences of the items name
|
||||||
for offset in match_indices(&text, finder, search_range) {
|
for offset in match_indices(&text, finder, search_range) {
|
||||||
for name in sema.find_nodes_at_offset_with_descend(&tree, offset) {
|
if let Some(iter) = find_nodes(name, &tree, offset) {
|
||||||
|
for name in iter.filter_map(ast::NameLike::cast) {
|
||||||
if match name {
|
if match name {
|
||||||
ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink),
|
ast::NameLike::NameRef(name_ref) => {
|
||||||
|
self.found_name_ref(&name_ref, sink)
|
||||||
|
}
|
||||||
ast::NameLike::Name(name) => self.found_name(&name, sink),
|
ast::NameLike::Name(name) => self.found_name(&name, sink),
|
||||||
ast::NameLike::Lifetime(lifetime) => self.found_lifetime(&lifetime, sink),
|
ast::NameLike::Lifetime(lifetime) => {
|
||||||
|
self.found_lifetime(&lifetime, sink)
|
||||||
|
}
|
||||||
} {
|
} {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Search for occurrences of the `Self` referring to our type
|
// Search for occurrences of the `Self` referring to our type
|
||||||
if let Some((self_ty, finder)) = &include_self_kw_refs {
|
if let Some((self_ty, finder)) = &include_self_kw_refs {
|
||||||
for offset in match_indices(&text, finder, search_range) {
|
for offset in match_indices(&text, finder, search_range) {
|
||||||
for name_ref in sema.find_nodes_at_offset_with_descend(&tree, offset) {
|
if let Some(iter) = find_nodes("Self", &tree, offset) {
|
||||||
|
for name_ref in iter.filter_map(ast::NameRef::cast) {
|
||||||
if self.found_self_ty_name_ref(self_ty, &name_ref, sink) {
|
if self.found_self_ty_name_ref(self_ty, &name_ref, sink) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -478,6 +491,7 @@ impl<'a> FindUsages<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Search for `super` and `crate` resolving to our module
|
// Search for `super` and `crate` resolving to our module
|
||||||
match self.def {
|
match self.def {
|
||||||
|
@ -493,15 +507,18 @@ impl<'a> FindUsages<'a> {
|
||||||
let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());
|
let tree = Lazy::new(move || sema.parse(file_id).syntax().clone());
|
||||||
|
|
||||||
for offset in match_indices(&text, finder, search_range) {
|
for offset in match_indices(&text, finder, search_range) {
|
||||||
for name_ref in sema.find_nodes_at_offset_with_descend(&tree, offset) {
|
if let Some(iter) = find_nodes("super", &tree, offset) {
|
||||||
|
for name_ref in iter.filter_map(ast::NameRef::cast) {
|
||||||
if self.found_name_ref(&name_ref, sink) {
|
if self.found_name_ref(&name_ref, sink) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if let Some(finder) = &is_crate_root {
|
if let Some(finder) = &is_crate_root {
|
||||||
for offset in match_indices(&text, finder, search_range) {
|
for offset in match_indices(&text, finder, search_range) {
|
||||||
for name_ref in sema.find_nodes_at_offset_with_descend(&tree, offset) {
|
if let Some(iter) = find_nodes("crate", &tree, offset) {
|
||||||
|
for name_ref in iter.filter_map(ast::NameRef::cast) {
|
||||||
if self.found_name_ref(&name_ref, sink) {
|
if self.found_name_ref(&name_ref, sink) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -510,6 +527,7 @@ impl<'a> FindUsages<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -544,13 +562,15 @@ impl<'a> FindUsages<'a> {
|
||||||
let finder = &Finder::new("self");
|
let finder = &Finder::new("self");
|
||||||
|
|
||||||
for offset in match_indices(&text, finder, search_range) {
|
for offset in match_indices(&text, finder, search_range) {
|
||||||
for name_ref in sema.find_nodes_at_offset_with_descend(&tree, offset) {
|
if let Some(iter) = find_nodes("self", &tree, offset) {
|
||||||
|
for name_ref in iter.filter_map(ast::NameRef::cast) {
|
||||||
if self.found_self_module_name_ref(&name_ref, sink) {
|
if self.found_self_module_name_ref(&name_ref, sink) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,8 +73,8 @@ impl MonikerResult {
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct PackageInformation {
|
pub struct PackageInformation {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub repo: String,
|
pub repo: Option<String>,
|
||||||
pub version: String,
|
pub version: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn crate_for_file(db: &RootDatabase, file_id: FileId) -> Option<Crate> {
|
pub(crate) fn crate_for_file(db: &RootDatabase, file_id: FileId) -> Option<Crate> {
|
||||||
|
@ -256,18 +256,18 @@ pub(crate) fn def_to_moniker(
|
||||||
let (name, repo, version) = match krate.origin(db) {
|
let (name, repo, version) = match krate.origin(db) {
|
||||||
CrateOrigin::CratesIo { repo, name } => (
|
CrateOrigin::CratesIo { repo, name } => (
|
||||||
name.unwrap_or(krate.display_name(db)?.canonical_name().to_string()),
|
name.unwrap_or(krate.display_name(db)?.canonical_name().to_string()),
|
||||||
repo?,
|
repo,
|
||||||
krate.version(db)?,
|
krate.version(db),
|
||||||
),
|
),
|
||||||
CrateOrigin::Lang(lang) => (
|
CrateOrigin::Lang(lang) => (
|
||||||
krate.display_name(db)?.canonical_name().to_string(),
|
krate.display_name(db)?.canonical_name().to_string(),
|
||||||
"https://github.com/rust-lang/rust/".to_string(),
|
Some("https://github.com/rust-lang/rust/".to_string()),
|
||||||
match lang {
|
Some(match lang {
|
||||||
LangCrateOrigin::Other => {
|
LangCrateOrigin::Other => {
|
||||||
"https://github.com/rust-lang/rust/library/".into()
|
"https://github.com/rust-lang/rust/library/".into()
|
||||||
}
|
}
|
||||||
lang => format!("https://github.com/rust-lang/rust/library/{lang}",),
|
lang => format!("https://github.com/rust-lang/rust/library/{lang}",),
|
||||||
},
|
}),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
PackageInformation { name, repo, version }
|
PackageInformation { name, repo, version }
|
||||||
|
@ -315,7 +315,7 @@ pub mod module {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
"foo::module::func",
|
"foo::module::func",
|
||||||
r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
|
r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
|
||||||
MonikerKind::Import,
|
MonikerKind::Import,
|
||||||
);
|
);
|
||||||
check_moniker(
|
check_moniker(
|
||||||
|
@ -331,7 +331,7 @@ pub mod module {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
"foo::module::func",
|
"foo::module::func",
|
||||||
r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
|
r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
|
||||||
MonikerKind::Export,
|
MonikerKind::Export,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -348,7 +348,7 @@ pub mod module {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
"foo::module::MyTrait::func",
|
"foo::module::MyTrait::func",
|
||||||
r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
|
r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
|
||||||
MonikerKind::Export,
|
MonikerKind::Export,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -365,7 +365,7 @@ pub mod module {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
"foo::module::MyTrait::MY_CONST",
|
"foo::module::MyTrait::MY_CONST",
|
||||||
r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
|
r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
|
||||||
MonikerKind::Export,
|
MonikerKind::Export,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -382,7 +382,7 @@ pub mod module {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
"foo::module::MyTrait::MyType",
|
"foo::module::MyTrait::MyType",
|
||||||
r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
|
r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
|
||||||
MonikerKind::Export,
|
MonikerKind::Export,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -405,7 +405,7 @@ pub mod module {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
"foo::module::MyStruct::MyTrait::func",
|
"foo::module::MyStruct::MyTrait::func",
|
||||||
r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
|
r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
|
||||||
MonikerKind::Export,
|
MonikerKind::Export,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -425,7 +425,7 @@ pub struct St {
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
"foo::St::a",
|
"foo::St::a",
|
||||||
r#"PackageInformation { name: "foo", repo: "https://a.b/foo.git", version: "0.1.0" }"#,
|
r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
|
||||||
MonikerKind::Import,
|
MonikerKind::Import,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,9 @@ pub(crate) fn prepare_rename(
|
||||||
if def.range_for_rename(&sema).is_none() {
|
if def.range_for_rename(&sema).is_none() {
|
||||||
bail!("No references found at position")
|
bail!("No references found at position")
|
||||||
}
|
}
|
||||||
let frange = sema.original_range(name_like.syntax());
|
let Some(frange) = sema.original_range_opt(name_like.syntax()) else {
|
||||||
|
bail!("No references found at position");
|
||||||
|
};
|
||||||
|
|
||||||
always!(
|
always!(
|
||||||
frange.range.contains_inclusive(position.offset)
|
frange.range.contains_inclusive(position.offset)
|
||||||
|
@ -51,7 +53,7 @@ pub(crate) fn prepare_rename(
|
||||||
.reduce(|acc, cur| match (acc, cur) {
|
.reduce(|acc, cur| match (acc, cur) {
|
||||||
// ensure all ranges are the same
|
// ensure all ranges are the same
|
||||||
(Ok(acc_inner), Ok(cur_inner)) if acc_inner == cur_inner => Ok(acc_inner),
|
(Ok(acc_inner), Ok(cur_inner)) if acc_inner == cur_inner => Ok(acc_inner),
|
||||||
(Err(e), _) => Err(e),
|
(e @ Err(_), _) | (_, e @ Err(_)) => e,
|
||||||
_ => bail!("inconsistent text range"),
|
_ => bail!("inconsistent text range"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2249,4 +2251,33 @@ fn foo((bar | bar | bar): ()) {
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn regression_13498() {
|
||||||
|
check(
|
||||||
|
"Testing",
|
||||||
|
r"
|
||||||
|
mod foo {
|
||||||
|
pub struct Test$0;
|
||||||
|
}
|
||||||
|
|
||||||
|
use foo::Test as Tester;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let t = Tester;
|
||||||
|
}
|
||||||
|
",
|
||||||
|
r"
|
||||||
|
mod foo {
|
||||||
|
pub struct Testing;
|
||||||
|
}
|
||||||
|
|
||||||
|
use foo::Testing as Tester;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let t = Tester;
|
||||||
|
}
|
||||||
|
",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,7 +149,7 @@ fn signature_help_for_call(
|
||||||
variant.name(db)
|
variant.name(db)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
hir::CallableKind::Closure | hir::CallableKind::FnPtr => (),
|
hir::CallableKind::Closure | hir::CallableKind::FnPtr | hir::CallableKind::Other => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
res.signature.push('(');
|
res.signature.push('(');
|
||||||
|
@ -189,9 +189,10 @@ fn signature_help_for_call(
|
||||||
hir::CallableKind::Function(func) if callable.return_type().contains_unknown() => {
|
hir::CallableKind::Function(func) if callable.return_type().contains_unknown() => {
|
||||||
render(func.ret_type(db))
|
render(func.ret_type(db))
|
||||||
}
|
}
|
||||||
hir::CallableKind::Function(_) | hir::CallableKind::Closure | hir::CallableKind::FnPtr => {
|
hir::CallableKind::Function(_)
|
||||||
render(callable.return_type())
|
| hir::CallableKind::Closure
|
||||||
}
|
| hir::CallableKind::FnPtr
|
||||||
|
| hir::CallableKind::Other => render(callable.return_type()),
|
||||||
hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {}
|
hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {}
|
||||||
}
|
}
|
||||||
Some(res)
|
Some(res)
|
||||||
|
@ -387,10 +388,9 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check(ra_fixture: &str, expect: Expect) {
|
fn check(ra_fixture: &str, expect: Expect) {
|
||||||
// Implicitly add `Sized` to avoid noisy `T: ?Sized` in the results.
|
|
||||||
let fixture = format!(
|
let fixture = format!(
|
||||||
r#"
|
r#"
|
||||||
#[lang = "sized"] trait Sized {{}}
|
//- minicore: sized, fn
|
||||||
{ra_fixture}
|
{ra_fixture}
|
||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
|
@ -1331,4 +1331,19 @@ fn f() {
|
||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_for_generic_call() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn f<F: FnOnce(u8, u16) -> i32>(f: F) {
|
||||||
|
f($0)
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
(u8, u16) -> i32
|
||||||
|
^^ ---
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,12 +106,12 @@ impl LsifManager<'_> {
|
||||||
manager: "cargo".to_string(),
|
manager: "cargo".to_string(),
|
||||||
uri: None,
|
uri: None,
|
||||||
content: None,
|
content: None,
|
||||||
repository: Some(lsif::Repository {
|
repository: pi.repo.map(|url| lsif::Repository {
|
||||||
url: pi.repo,
|
url,
|
||||||
r#type: "git".to_string(),
|
r#type: "git".to_string(),
|
||||||
commit_id: None,
|
commit_id: None,
|
||||||
}),
|
}),
|
||||||
version: Some(pi.version),
|
version: pi.version,
|
||||||
}));
|
}));
|
||||||
self.package_map.insert(package_information, result_set_id);
|
self.package_map.insert(package_information, result_set_id);
|
||||||
result_set_id
|
result_set_id
|
||||||
|
|
|
@ -231,7 +231,7 @@ fn token_to_symbol(token: &TokenStaticData) -> Option<scip_types::Symbol> {
|
||||||
package: Some(scip_types::Package {
|
package: Some(scip_types::Package {
|
||||||
manager: "cargo".to_string(),
|
manager: "cargo".to_string(),
|
||||||
name: package_name,
|
name: package_name,
|
||||||
version,
|
version: version.unwrap_or_else(|| ".".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.into(),
|
.into(),
|
||||||
|
@ -415,4 +415,42 @@ pub mod module {
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn global_symbol_for_pub_struct() {
|
||||||
|
check_symbol(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs crate:main
|
||||||
|
mod foo;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let _bar = foo::Bar { i: 0 };
|
||||||
|
}
|
||||||
|
//- /foo.rs
|
||||||
|
pub struct Bar$0 {
|
||||||
|
pub i: i32,
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
"rust-analyzer cargo main . foo/Bar#",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn global_symbol_for_pub_struct_reference() {
|
||||||
|
check_symbol(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs crate:main
|
||||||
|
mod foo;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let _bar = foo::Bar$0 { i: 0 };
|
||||||
|
}
|
||||||
|
//- /foo.rs
|
||||||
|
pub struct Bar {
|
||||||
|
pub i: i32,
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
"rust-analyzer cargo main . foo/Bar#",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,9 @@ mod patch_old_style;
|
||||||
// parsing the old name.
|
// parsing the old name.
|
||||||
config_data! {
|
config_data! {
|
||||||
struct ConfigData {
|
struct ConfigData {
|
||||||
|
/// Whether to insert #[must_use] when generating `as_` methods
|
||||||
|
/// for enum variants.
|
||||||
|
assist_emitMustUse: bool = "false",
|
||||||
/// Placeholder expression to use for missing expressions in assists.
|
/// Placeholder expression to use for missing expressions in assists.
|
||||||
assist_expressionFillDefault: ExprFillDefaultDef = "\"todo\"",
|
assist_expressionFillDefault: ExprFillDefaultDef = "\"todo\"",
|
||||||
|
|
||||||
|
@ -1276,6 +1279,7 @@ impl Config {
|
||||||
allowed: None,
|
allowed: None,
|
||||||
insert_use: self.insert_use_config(),
|
insert_use: self.insert_use_config(),
|
||||||
prefer_no_std: self.data.imports_prefer_no_std,
|
prefer_no_std: self.data.imports_prefer_no_std,
|
||||||
|
assist_emit_must_use: self.data.assist_emitMustUse,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -334,6 +334,10 @@ pub fn block_expr(
|
||||||
ast_from_text(&format!("fn f() {buf}"))
|
ast_from_text(&format!("fn f() {buf}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tail_only_block_expr(tail_expr: ast::Expr) -> ast::BlockExpr {
|
||||||
|
ast_from_text(&format!("fn f() {{ {tail_expr} }}"))
|
||||||
|
}
|
||||||
|
|
||||||
/// Ideally this function wouldn't exist since it involves manual indenting.
|
/// Ideally this function wouldn't exist since it involves manual indenting.
|
||||||
/// It differs from `make::block_expr` by also supporting comments.
|
/// It differs from `make::block_expr` by also supporting comments.
|
||||||
///
|
///
|
||||||
|
@ -656,6 +660,22 @@ pub fn let_stmt(
|
||||||
};
|
};
|
||||||
ast_from_text(&format!("fn f() {{ {text} }}"))
|
ast_from_text(&format!("fn f() {{ {text} }}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn let_else_stmt(
|
||||||
|
pattern: ast::Pat,
|
||||||
|
ty: Option<ast::Type>,
|
||||||
|
expr: ast::Expr,
|
||||||
|
diverging: ast::BlockExpr,
|
||||||
|
) -> ast::LetStmt {
|
||||||
|
let mut text = String::new();
|
||||||
|
format_to!(text, "let {pattern}");
|
||||||
|
if let Some(ty) = ty {
|
||||||
|
format_to!(text, ": {ty}");
|
||||||
|
}
|
||||||
|
format_to!(text, " = {expr} else {diverging};");
|
||||||
|
ast_from_text(&format!("fn f() {{ {text} }}"))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn expr_stmt(expr: ast::Expr) -> ast::ExprStmt {
|
pub fn expr_stmt(expr: ast::Expr) -> ast::ExprStmt {
|
||||||
let semi = if expr.is_block_like() { "" } else { ";" };
|
let semi = if expr.is_block_like() { "" } else { ";" };
|
||||||
ast_from_text(&format!("fn f() {{ {expr}{semi} (); }}"))
|
ast_from_text(&format!("fn f() {{ {expr}{semi} (); }}"))
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue