Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit ffe908d

Browse files
Report unresolved idents for implicit captures in format_args!()
And also a bit of cleanup by storing the capture's span with the open quote included.
1 parent 5c6bae0 commit ffe908d

File tree

7 files changed

+110
-63
lines changed

7 files changed

+110
-63
lines changed

src/tools/rust-analyzer/crates/hir-def/src/body.rs

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use smallvec::SmallVec;
1818
use span::{Edition, MacroFileId};
1919
use syntax::{ast, AstPtr, SyntaxNodePtr};
2020
use triomphe::Arc;
21+
use tt::TextRange;
2122

2223
use crate::{
2324
db::DefDatabase,
@@ -143,15 +144,7 @@ pub struct BodySourceMap {
143144

144145
pub types: TypesSourceMap,
145146

146-
// FIXME: Make this a sane struct.
147-
template_map: Option<
148-
Box<(
149-
// format_args!
150-
FxHashMap<ExprId, (HygieneId, Vec<(syntax::TextRange, Name)>)>,
151-
// asm!
152-
FxHashMap<ExprId, Vec<Vec<(syntax::TextRange, usize)>>>,
153-
)>,
154-
>,
147+
template_map: Option<Box<FormatTemplate>>,
155148

156149
expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, MacroFileId>,
157150

@@ -160,6 +153,20 @@ pub struct BodySourceMap {
160153
diagnostics: Vec<BodyDiagnostic>,
161154
}
162155

156+
#[derive(Default, Debug, Eq, PartialEq)]
157+
struct FormatTemplate {
158+
/// A map from `format_args!()` expressions to their captures.
159+
format_args_to_captures: FxHashMap<ExprId, (HygieneId, Vec<(syntax::TextRange, Name)>)>,
160+
/// A map from `asm!()` expressions to their captures.
161+
asm_to_captures: FxHashMap<ExprId, Vec<Vec<(syntax::TextRange, usize)>>>,
162+
/// A map from desugared expressions of implicit captures to their source.
163+
///
164+
/// The value stored for each capture is its template literal and offset inside it. The template literal
165+
/// is from the `format_args[_nl]!()` macro and so needs to be mapped up once to go to the user-written
166+
/// template.
167+
implicit_capture_to_source: FxHashMap<ExprId, InFile<(AstPtr<ast::Expr>, TextRange)>>,
168+
}
169+
163170
#[derive(Debug, Eq, PartialEq)]
164171
pub enum BodyDiagnostic {
165172
InactiveCode { node: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions },
@@ -798,18 +805,29 @@ impl BodySourceMap {
798805
node: InFile<&ast::FormatArgsExpr>,
799806
) -> Option<(HygieneId, &[(syntax::TextRange, Name)])> {
800807
let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::Expr>);
801-
let (hygiene, names) =
802-
self.template_map.as_ref()?.0.get(&self.expr_map.get(&src)?.as_expr()?)?;
808+
let (hygiene, names) = self
809+
.template_map
810+
.as_ref()?
811+
.format_args_to_captures
812+
.get(&self.expr_map.get(&src)?.as_expr()?)?;
803813
Some((*hygiene, &**names))
804814
}
805815

816+
pub fn format_args_implicit_capture(
817+
&self,
818+
capture_expr: ExprId,
819+
) -> Option<InFile<(AstPtr<ast::Expr>, TextRange)>> {
820+
self.template_map.as_ref()?.implicit_capture_to_source.get(&capture_expr).copied()
821+
}
822+
806823
pub fn asm_template_args(
807824
&self,
808825
node: InFile<&ast::AsmExpr>,
809826
) -> Option<(ExprId, &[Vec<(syntax::TextRange, usize)>])> {
810827
let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::Expr>);
811828
let expr = self.expr_map.get(&src)?.as_expr()?;
812-
Some(expr).zip(self.template_map.as_ref()?.1.get(&expr).map(std::ops::Deref::deref))
829+
Some(expr)
830+
.zip(self.template_map.as_ref()?.asm_to_captures.get(&expr).map(std::ops::Deref::deref))
813831
}
814832

815833
/// Get a reference to the body source map's diagnostics.
@@ -835,8 +853,14 @@ impl BodySourceMap {
835853
types,
836854
} = self;
837855
if let Some(template_map) = template_map {
838-
template_map.0.shrink_to_fit();
839-
template_map.1.shrink_to_fit();
856+
let FormatTemplate {
857+
format_args_to_captures,
858+
asm_to_captures,
859+
implicit_capture_to_source,
860+
} = &mut **template_map;
861+
format_args_to_captures.shrink_to_fit();
862+
asm_to_captures.shrink_to_fit();
863+
implicit_capture_to_source.shrink_to_fit();
840864
}
841865
expr_map.shrink_to_fit();
842866
expr_map_back.shrink_to_fit();

src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1957,17 +1957,29 @@ impl ExprCollector<'_> {
19571957
_ => None,
19581958
});
19591959
let mut mappings = vec![];
1960-
let (fmt, hygiene) = match template.and_then(|it| self.expand_macros_to_string(it)) {
1961-
Some((s, is_direct_literal)) => {
1960+
let (fmt, hygiene) = match template.and_then(|template| {
1961+
self.expand_macros_to_string(template.clone()).map(|it| (it, template))
1962+
}) {
1963+
Some(((s, is_direct_literal), template)) => {
19621964
let call_ctx = self.expander.syntax_context();
19631965
let hygiene = self.hygiene_id_for(s.syntax().text_range().start());
19641966
let fmt = format_args::parse(
19651967
&s,
19661968
fmt_snippet,
19671969
args,
19681970
is_direct_literal,
1969-
|name| {
1971+
|name, range| {
19701972
let expr_id = self.alloc_expr_desugared(Expr::Path(Path::from(name)));
1973+
if let Some(range) = range {
1974+
self.source_map
1975+
.template_map
1976+
.get_or_insert_with(Default::default)
1977+
.implicit_capture_to_source
1978+
.insert(
1979+
expr_id,
1980+
self.expander.in_file((AstPtr::new(&template), range)),
1981+
);
1982+
}
19711983
if !hygiene.is_root() {
19721984
self.body.expr_hygiene.insert(expr_id, hygiene);
19731985
}
@@ -2139,7 +2151,7 @@ impl ExprCollector<'_> {
21392151
self.source_map
21402152
.template_map
21412153
.get_or_insert_with(Default::default)
2142-
.0
2154+
.format_args_to_captures
21432155
.insert(idx, (hygiene, mappings));
21442156
idx
21452157
}

src/tools/rust-analyzer/crates/hir-def/src/body/lower/asm.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use syntax::{
66
ast::{self, HasName, IsString},
77
AstNode, AstPtr, AstToken, T,
88
};
9-
use tt::{TextRange, TextSize};
9+
use tt::TextRange;
1010

1111
use crate::{
1212
body::lower::{ExprCollector, FxIndexSet},
@@ -224,7 +224,7 @@ impl ExprCollector<'_> {
224224
TextRange::new(
225225
inner_span.start.try_into().unwrap(),
226226
inner_span.end.try_into().unwrap(),
227-
) - TextSize::from(str_style.map(|it| it + 1).unwrap_or(0) as u32 + 1)
227+
)
228228
})
229229
};
230230
for piece in unverified_pieces {
@@ -268,7 +268,11 @@ impl ExprCollector<'_> {
268268
Expr::InlineAsm(InlineAsm { operands: operands.into_boxed_slice(), options }),
269269
syntax_ptr,
270270
);
271-
self.source_map.template_map.get_or_insert_with(Default::default).1.insert(idx, mappings);
271+
self.source_map
272+
.template_map
273+
.get_or_insert_with(Default::default)
274+
.asm_to_captures
275+
.insert(idx, mappings);
272276
idx
273277
}
274278
}

src/tools/rust-analyzer/crates/hir-def/src/hir/format_args.rs

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
//! Parses `format_args` input.
22
3+
use either::Either;
34
use hir_expand::name::Name;
45
use intern::Symbol;
56
use rustc_parse_format as parse;
67
use span::SyntaxContextId;
78
use stdx::TupleExt;
89
use syntax::{
910
ast::{self, IsString},
10-
TextRange, TextSize,
11+
TextRange,
1112
};
1213

1314
use crate::hir::ExprId;
@@ -33,7 +34,7 @@ pub enum FormatArgsPiece {
3334
Placeholder(FormatPlaceholder),
3435
}
3536

36-
#[derive(Copy, Debug, Clone, PartialEq, Eq)]
37+
#[derive(Debug, Clone, PartialEq, Eq)]
3738
pub struct FormatPlaceholder {
3839
/// Index into [`FormatArgs::arguments`].
3940
pub argument: FormatArgPosition,
@@ -45,11 +46,11 @@ pub struct FormatPlaceholder {
4546
pub format_options: FormatOptions,
4647
}
4748

48-
#[derive(Copy, Debug, Clone, PartialEq, Eq)]
49+
#[derive(Debug, Clone, PartialEq, Eq)]
4950
pub struct FormatArgPosition {
5051
/// Which argument this position refers to (Ok),
5152
/// or would've referred to if it existed (Err).
52-
pub index: Result<usize, usize>,
53+
pub index: Result<usize, Either<usize, Name>>,
5354
/// What kind of position this is. See [`FormatArgPositionKind`].
5455
pub kind: FormatArgPositionKind,
5556
/// The span of the name or number.
@@ -88,7 +89,7 @@ pub enum FormatTrait {
8889
UpperHex,
8990
}
9091

91-
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
92+
#[derive(Clone, Default, Debug, PartialEq, Eq)]
9293
pub struct FormatOptions {
9394
/// The width. E.g. `{:5}` or `{:width$}`.
9495
pub width: Option<FormatCount>,
@@ -133,7 +134,7 @@ pub enum FormatAlignment {
133134
Center,
134135
}
135136

136-
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
137+
#[derive(Clone, Debug, PartialEq, Eq)]
137138
pub enum FormatCount {
138139
/// `{:5}` or `{:.5}`
139140
Literal(usize),
@@ -173,7 +174,7 @@ pub(crate) fn parse(
173174
fmt_snippet: Option<String>,
174175
mut args: FormatArgumentsCollector,
175176
is_direct_literal: bool,
176-
mut synth: impl FnMut(Name) -> ExprId,
177+
mut synth: impl FnMut(Name, Option<TextRange>) -> ExprId,
177178
mut record_usage: impl FnMut(Name, Option<TextRange>),
178179
call_ctx: SyntaxContextId,
179180
) -> FormatArgs {
@@ -192,7 +193,6 @@ pub(crate) fn parse(
192193
}
193194
None => None,
194195
};
195-
196196
let mut parser =
197197
parse::Parser::new(&text, str_style, fmt_snippet, false, parse::ParseMode::Format);
198198

@@ -217,7 +217,6 @@ pub(crate) fn parse(
217217
let to_span = |inner_span: parse::InnerSpan| {
218218
is_source_literal.then(|| {
219219
TextRange::new(inner_span.start.try_into().unwrap(), inner_span.end.try_into().unwrap())
220-
- TextSize::from(str_style.map(|it| it + 1).unwrap_or(0) as u32 + 1)
221220
})
222221
};
223222

@@ -245,8 +244,8 @@ pub(crate) fn parse(
245244
Ok(index)
246245
} else {
247246
// Doesn't exist as an explicit argument.
248-
invalid_refs.push((index, span, used_as, kind));
249-
Err(index)
247+
invalid_refs.push((Either::Left(index), span, used_as, kind));
248+
Err(Either::Left(index))
250249
}
251250
}
252251
ArgRef::Name(name, span) => {
@@ -265,14 +264,17 @@ pub(crate) fn parse(
265264
// For the moment capturing variables from format strings expanded from macros is
266265
// disabled (see RFC #2795)
267266
// FIXME: Diagnose
267+
invalid_refs.push((Either::Right(name.clone()), span, used_as, kind));
268+
Err(Either::Right(name))
269+
} else {
270+
record_usage(name.clone(), span);
271+
Ok(args.add(FormatArgument {
272+
kind: FormatArgumentKind::Captured(name.clone()),
273+
// FIXME: This is problematic, we might want to synthesize a dummy
274+
// expression proper and/or desugar these.
275+
expr: synth(name, span),
276+
}))
268277
}
269-
record_usage(name.clone(), span);
270-
Ok(args.add(FormatArgument {
271-
kind: FormatArgumentKind::Captured(name.clone()),
272-
// FIXME: This is problematic, we might want to synthesize a dummy
273-
// expression proper and/or desugar these.
274-
expr: synth(name),
275-
}))
276278
}
277279
}
278280
};

src/tools/rust-analyzer/crates/hir/src/diagnostics.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ pub struct UnresolvedAssocItem {
262262

263263
#[derive(Debug)]
264264
pub struct UnresolvedIdent {
265-
pub expr_or_pat: InFile<AstPtr<Either<ast::Expr, ast::Pat>>>,
265+
pub node: InFile<(AstPtr<Either<ast::Expr, ast::Pat>>, Option<TextRange>)>,
266266
}
267267

268268
#[derive(Debug)]
@@ -550,11 +550,10 @@ impl AnyDiagnostic {
550550
source_map: &hir_def::body::BodySourceMap,
551551
) -> Option<AnyDiagnostic> {
552552
let expr_syntax = |expr| {
553-
source_map.expr_syntax(expr).inspect_err(|_| tracing::error!("synthetic syntax")).ok()
554-
};
555-
let pat_syntax = |pat| {
556-
source_map.pat_syntax(pat).inspect_err(|_| tracing::error!("synthetic syntax")).ok()
553+
source_map.expr_syntax(expr).inspect_err(|_| stdx::never!("synthetic syntax")).ok()
557554
};
555+
let pat_syntax =
556+
|pat| source_map.pat_syntax(pat).inspect_err(|_| stdx::never!("synthetic syntax")).ok();
558557
let expr_or_pat_syntax = |id| match id {
559558
ExprOrPatId::ExprId(expr) => expr_syntax(expr).map(|it| it.map(AstPtr::wrap_left)),
560559
ExprOrPatId::PatId(pat) => pat_syntax(pat),
@@ -626,8 +625,16 @@ impl AnyDiagnostic {
626625
UnresolvedAssocItem { expr_or_pat }.into()
627626
}
628627
&InferenceDiagnostic::UnresolvedIdent { id } => {
629-
let expr_or_pat = expr_or_pat_syntax(id)?;
630-
UnresolvedIdent { expr_or_pat }.into()
628+
let node = match id {
629+
ExprOrPatId::ExprId(id) => match source_map.expr_syntax(id) {
630+
Ok(syntax) => syntax.map(|it| (it.wrap_left(), None)),
631+
Err(SyntheticSyntax) => source_map
632+
.format_args_implicit_capture(id)?
633+
.map(|(node, range)| (node.wrap_left(), Some(range))),
634+
},
635+
ExprOrPatId::PatId(id) => pat_syntax(id)?.map(|it| (it, None)),
636+
};
637+
UnresolvedIdent { node }.into()
631638
}
632639
&InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break, bad_value_break } => {
633640
let expr = expr_syntax(expr)?;

src/tools/rust-analyzer/crates/hir/src/semantics.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use span::{AstIdMap, EditionedFileId, FileId, HirFileIdRepr, SyntaxContextId};
3838
use stdx::TupleExt;
3939
use syntax::{
4040
algo::skip_trivia_token,
41-
ast::{self, HasAttrs as _, HasGenericParams, IsString as _},
41+
ast::{self, HasAttrs as _, HasGenericParams},
4242
AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange,
4343
TextSize,
4444
};
@@ -643,8 +643,7 @@ impl<'db> SemanticsImpl<'db> {
643643
&self,
644644
string: &ast::String,
645645
) -> Option<Vec<(TextRange, Option<Either<PathResolution, InlineAsmOperand>>)>> {
646-
let quote = string.open_quote_text_range()?;
647-
646+
let string_start = string.syntax().text_range().start();
648647
let token = self.wrap_token_infile(string.syntax().clone()).into_real_file().ok()?;
649648
self.descend_into_macros_breakable(token, |token, _| {
650649
(|| {
@@ -658,7 +657,7 @@ impl<'db> SemanticsImpl<'db> {
658657
let format_args = self.wrap_node_infile(format_args);
659658
let res = source_analyzer
660659
.as_format_args_parts(self.db, format_args.as_ref())?
661-
.map(|(range, res)| (range + quote.end(), res.map(Either::Left)))
660+
.map(|(range, res)| (range + string_start, res.map(Either::Left)))
662661
.collect();
663662
Some(res)
664663
} else {
@@ -672,7 +671,7 @@ impl<'db> SemanticsImpl<'db> {
672671
.iter()
673672
.map(|&(range, index)| {
674673
(
675-
range + quote.end(),
674+
range + string_start,
676675
Some(Either::Right(InlineAsmOperand { owner, expr, index })),
677676
)
678677
})
@@ -690,17 +689,16 @@ impl<'db> SemanticsImpl<'db> {
690689
original_token: SyntaxToken,
691690
offset: TextSize,
692691
) -> Option<(TextRange, Option<Either<PathResolution, InlineAsmOperand>>)> {
693-
let original_string = ast::String::cast(original_token.clone())?;
692+
let string_start = original_token.text_range().start();
694693
let original_token = self.wrap_token_infile(original_token).into_real_file().ok()?;
695-
let quote = original_string.open_quote_text_range()?;
696694
self.descend_into_macros_breakable(original_token, |token, _| {
697695
(|| {
698696
let token = token.value;
699697
self.resolve_offset_in_format_args(
700698
ast::String::cast(token)?,
701-
offset.checked_sub(quote.end())?,
699+
offset.checked_sub(string_start)?,
702700
)
703-
.map(|(range, res)| (range + quote.end(), res))
701+
.map(|(range, res)| (range + string_start, res))
704702
})()
705703
.map_or(ControlFlow::Continue(()), ControlFlow::Break)
706704
})

0 commit comments

Comments
 (0)