Skip to content

Commit

Permalink
Auto merge of rust-lang#16060 - Veykril:format-args-orphans, r=Veykril
Browse files Browse the repository at this point in the history
fix: Fix completion failing in `format_args!` with invalid template
  • Loading branch information
bors committed Dec 8, 2023
2 parents 0395328 + cf083fe commit 19387d3
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 8 deletions.
14 changes: 12 additions & 2 deletions crates/hir-def/src/body/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1611,7 +1611,11 @@ impl ExprCollector<'_> {
}
},
),
None => FormatArgs { template: Default::default(), arguments: args.finish() },
None => FormatArgs {
template: Default::default(),
arguments: args.finish(),
orphans: Default::default(),
},
};

// Create a list of all _unique_ (argument, format trait) combinations.
Expand Down Expand Up @@ -1750,7 +1754,13 @@ impl ExprCollector<'_> {
});
let unsafe_arg_new = self.alloc_expr_desugared(Expr::Unsafe {
id: None,
statements: Box::default(),
// We collect the unused expressions here so that we still infer them instead of
// dropping them out of the expression tree
statements: fmt
.orphans
.into_iter()
.map(|expr| Statement::Expr { expr, has_semi: true })
.collect(),
tail: Some(unsafe_arg_new),
});

Expand Down
14 changes: 12 additions & 2 deletions crates/hir-def/src/hir/format_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::mem;

use hir_expand::name::Name;
use rustc_dependencies::parse_format as parse;
use stdx::TupleExt;
use syntax::{
ast::{self, IsString},
SmolStr, TextRange, TextSize,
Expand All @@ -14,6 +15,7 @@ use crate::hir::ExprId;
pub struct FormatArgs {
pub template: Box<[FormatArgsPiece]>,
pub arguments: FormatArguments,
pub orphans: Vec<ExprId>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -196,7 +198,11 @@ pub(crate) fn parse(
let is_source_literal = parser.is_source_literal;
if !parser.errors.is_empty() {
// FIXME: Diagnose
return FormatArgs { template: Default::default(), arguments: args.finish() };
return FormatArgs {
template: Default::default(),
arguments: args.finish(),
orphans: vec![],
};
}

let to_span = |inner_span: parse::InnerSpan| {
Expand Down Expand Up @@ -419,7 +425,11 @@ pub(crate) fn parse(
// FIXME: Diagnose
}

FormatArgs { template: template.into_boxed_slice(), arguments: args.finish() }
FormatArgs {
template: template.into_boxed_slice(),
arguments: args.finish(),
orphans: unused.into_iter().map(TupleExt::head).collect(),
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down
154 changes: 154 additions & 0 deletions crates/ide-completion/src/tests/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1094,3 +1094,157 @@ pub struct UnstableButWeAreOnNightlyAnyway;
"#]],
);
}

#[test]
fn inside_format_args_completions_work() {
check_empty(
r#"
//- minicore: fmt
struct Foo;
impl Foo {
fn foo(&self) {}
}
fn main() {
format_args!("{}", Foo.$0);
}
"#,
expect![[r#"
me foo() fn(&self)
sn box Box::new(expr)
sn call function(expr)
sn dbg dbg!(expr)
sn dbgr dbg!(&expr)
sn match match expr {}
sn ref &expr
sn refm &mut expr
sn unsafe unsafe {}
"#]],
);
check_empty(
r#"
//- minicore: fmt
struct Foo;
impl Foo {
fn foo(&self) {}
}
fn main() {
format_args!("{}", Foo.f$0);
}
"#,
expect![[r#"
me foo() fn(&self)
sn box Box::new(expr)
sn call function(expr)
sn dbg dbg!(expr)
sn dbgr dbg!(&expr)
sn match match expr {}
sn ref &expr
sn refm &mut expr
sn unsafe unsafe {}
"#]],
);
}

#[test]
fn inside_faulty_format_args_completions_work() {
check_empty(
r#"
//- minicore: fmt
struct Foo;
impl Foo {
fn foo(&self) {}
}
fn main() {
format_args!("", Foo.$0);
}
"#,
expect![[r#"
me foo() fn(&self)
sn box Box::new(expr)
sn call function(expr)
sn dbg dbg!(expr)
sn dbgr dbg!(&expr)
sn match match expr {}
sn ref &expr
sn refm &mut expr
sn unsafe unsafe {}
"#]],
);
check_empty(
r#"
//- minicore: fmt
struct Foo;
impl Foo {
fn foo(&self) {}
}
fn main() {
format_args!("", Foo.f$0);
}
"#,
expect![[r#"
me foo() fn(&self)
sn box Box::new(expr)
sn call function(expr)
sn dbg dbg!(expr)
sn dbgr dbg!(&expr)
sn match match expr {}
sn ref &expr
sn refm &mut expr
sn unsafe unsafe {}
"#]],
);
check_empty(
r#"
//- minicore: fmt
struct Foo;
impl Foo {
fn foo(&self) {}
}
fn main() {
format_args!("{} {named} {captured} {named} {}", a, named = c, Foo.f$0);
}
"#,
expect![[r#"
me foo() fn(&self)
sn box Box::new(expr)
sn call function(expr)
sn dbg dbg!(expr)
sn dbgr dbg!(&expr)
sn match match expr {}
sn ref &expr
sn refm &mut expr
sn unsafe unsafe {}
"#]],
);
check_empty(
r#"
//- minicore: fmt
struct Foo;
impl Foo {
fn foo(&self) {}
}
fn main() {
format_args!("{", Foo.f$0);
}
"#,
expect![[r#"
sn box Box::new(expr)
sn call function(expr)
sn dbg dbg!(expr)
sn dbgr dbg!(&expr)
sn if if expr {}
sn match match expr {}
sn not !expr
sn ref &expr
sn refm &mut expr
sn unsafe unsafe {}
sn while while expr {}
"#]],
);
}
6 changes: 4 additions & 2 deletions crates/ide/src/view_hir.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use hir::{DefWithBody, Semantics};
use ide_db::base_db::FilePosition;
use ide_db::RootDatabase;
use syntax::{algo::find_node_at_offset, ast, AstNode};
use syntax::{algo::ancestors_at_offset, ast, AstNode};

// Feature: View Hir
//
Expand All @@ -19,7 +19,9 @@ fn body_hir(db: &RootDatabase, position: FilePosition) -> Option<String> {
let sema = Semantics::new(db);
let source_file = sema.parse(position.file_id);

let item = find_node_at_offset::<ast::Item>(source_file.syntax(), position.offset)?;
let item = ancestors_at_offset(source_file.syntax(), position.offset)
.filter(|it| ast::MacroCall::can_cast(it.kind()))
.find_map(ast::Item::cast)?;
let def: DefWithBody = match item {
ast::Item::Fn(it) => sema.to_def(&it)?.into(),
ast::Item::Const(it) => sema.to_def(&it)?.into(),
Expand Down
6 changes: 4 additions & 2 deletions crates/ide/src/view_mir.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use hir::{DefWithBody, Semantics};
use ide_db::base_db::FilePosition;
use ide_db::RootDatabase;
use syntax::{algo::find_node_at_offset, ast, AstNode};
use syntax::{algo::ancestors_at_offset, ast, AstNode};

// Feature: View Mir
//
Expand All @@ -18,7 +18,9 @@ fn body_mir(db: &RootDatabase, position: FilePosition) -> Option<String> {
let sema = Semantics::new(db);
let source_file = sema.parse(position.file_id);

let item = find_node_at_offset::<ast::Item>(source_file.syntax(), position.offset)?;
let item = ancestors_at_offset(source_file.syntax(), position.offset)
.filter(|it| ast::MacroCall::can_cast(it.kind()))
.find_map(ast::Item::cast)?;
let def: DefWithBody = match item {
ast::Item::Fn(it) => sema.to_def(&it)?.into(),
ast::Item::Const(it) => sema.to_def(&it)?.into(),
Expand Down
1 change: 1 addition & 0 deletions crates/rust-analyzer/tests/slow-tests/tidy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ fn check_dbg(path: &Path, text: &str) {
// We have .dbg postfix
"ide-completion/src/completions/postfix.rs",
"ide-completion/src/completions/keyword.rs",
"ide-completion/src/tests/expression.rs",
"ide-completion/src/tests/proc_macros.rs",
// The documentation in string literals may contain anything for its own purposes
"ide-completion/src/lib.rs",
Expand Down

0 comments on commit 19387d3

Please sign in to comment.