Skip to content

Commit 8a7958d

Browse files
committed
Implicit format args support
1 parent 0b8a4ef commit 8a7958d

37 files changed

+615
-174
lines changed

crates/hir-def/src/body.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ pub struct BodySourceMap {
9595
field_map_back: FxHashMap<ExprId, FieldSource>,
9696
pat_field_map_back: FxHashMap<PatId, PatFieldSource>,
9797

98+
format_args_template_map: FxHashMap<ExprId, Vec<(syntax::TextRange, Name)>>,
99+
98100
expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>,
99101

100102
/// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in
@@ -387,6 +389,14 @@ impl BodySourceMap {
387389
self.expr_map.get(&src).copied()
388390
}
389391

392+
pub fn implicit_format_args(
393+
&self,
394+
node: InFile<&ast::FormatArgsExpr>,
395+
) -> Option<&[(syntax::TextRange, Name)]> {
396+
let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::Expr>);
397+
self.format_args_template_map.get(self.expr_map.get(&src)?).map(std::ops::Deref::deref)
398+
}
399+
390400
/// Get a reference to the body source map's diagnostics.
391401
pub fn diagnostics(&self) -> &[BodyDiagnostic] {
392402
&self.diagnostics
@@ -403,8 +413,10 @@ impl BodySourceMap {
403413
field_map_back,
404414
pat_field_map_back,
405415
expansions,
416+
format_args_template_map,
406417
diagnostics,
407418
} = self;
419+
format_args_template_map.shrink_to_fit();
408420
expr_map.shrink_to_fit();
409421
expr_map_back.shrink_to_fit();
410422
pat_map.shrink_to_fit();

crates/hir-def/src/body/lower.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1597,12 +1597,20 @@ impl ExprCollector<'_> {
15971597
});
15981598
let template = f.template();
15991599
let fmt_snippet = template.as_ref().map(ToString::to_string);
1600+
let mut mappings = vec![];
16001601
let fmt = match template.and_then(|it| self.expand_macros_to_string(it)) {
1601-
Some((s, is_direct_literal)) => {
1602-
format_args::parse(&s, fmt_snippet, args, is_direct_literal, |name| {
1603-
self.alloc_expr_desugared(Expr::Path(Path::from(name)))
1604-
})
1605-
}
1602+
Some((s, is_direct_literal)) => format_args::parse(
1603+
&s,
1604+
fmt_snippet,
1605+
args,
1606+
is_direct_literal,
1607+
|name| self.alloc_expr_desugared(Expr::Path(Path::from(name))),
1608+
|name, span| {
1609+
if let Some(span) = span {
1610+
mappings.push((span, name.clone()))
1611+
}
1612+
},
1613+
),
16061614
None => FormatArgs { template: Default::default(), arguments: args.finish() },
16071615
};
16081616

@@ -1746,14 +1754,16 @@ impl ExprCollector<'_> {
17461754
tail: Some(unsafe_arg_new),
17471755
});
17481756

1749-
self.alloc_expr(
1757+
let idx = self.alloc_expr(
17501758
Expr::Call {
17511759
callee: new_v1_formatted,
17521760
args: Box::new([lit_pieces, args, format_options, unsafe_arg_new]),
17531761
is_assignee_expr: false,
17541762
},
17551763
syntax_ptr,
1756-
)
1764+
);
1765+
self.source_map.format_args_template_map.insert(idx, mappings);
1766+
idx
17571767
}
17581768

17591769
/// Generate a hir expression for a format_args placeholder specification.

crates/hir-def/src/body/tests.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ fn main() {
160160
let count = 10;
161161
builtin#lang(Arguments::new_v1_formatted)(
162162
&[
163-
"\"hello ", " ", " friends, we ", " ", "", "\"",
163+
"hello ", " ", " friends, we ", " ", "",
164164
],
165165
&[
166166
builtin#lang(Argument::new_display)(
@@ -261,7 +261,7 @@ impl SsrError {
261261
_ = $crate::error::SsrError::new(
262262
builtin#lang(Arguments::new_v1_formatted)(
263263
&[
264-
"\"Failed to resolve path `", "`\"",
264+
"Failed to resolve path `", "`",
265265
],
266266
&[
267267
builtin#lang(Argument::new_display)(
@@ -320,7 +320,7 @@ fn f() {
320320
$crate::panicking::panic_fmt(
321321
builtin#lang(Arguments::new_v1_formatted)(
322322
&[
323-
"\"cc\"",
323+
"cc",
324324
],
325325
&[],
326326
&[],

crates/hir-def/src/hir/format_args.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use hir_expand::name::Name;
55
use rustc_dependencies::parse_format as parse;
66
use syntax::{
77
ast::{self, IsString},
8-
AstToken, SmolStr, TextRange,
8+
SmolStr, TextRange, TextSize,
99
};
1010

1111
use crate::hir::ExprId;
@@ -170,15 +170,18 @@ pub(crate) fn parse(
170170
mut args: FormatArgumentsCollector,
171171
is_direct_literal: bool,
172172
mut synth: impl FnMut(Name) -> ExprId,
173+
mut record_usage: impl FnMut(Name, Option<TextRange>),
173174
) -> FormatArgs {
174-
let text = s.text();
175+
let text = s.text_without_quotes();
175176
let str_style = match s.quote_offsets() {
176177
Some(offsets) => {
177178
let raw = u32::from(offsets.quotes.0.len()) - 1;
178-
(raw != 0).then_some(raw as usize)
179+
// subtract 1 for the `r` prefix
180+
(raw != 0).then(|| raw as usize - 1)
179181
}
180182
None => None,
181183
};
184+
182185
let mut parser =
183186
parse::Parser::new(text, str_style, fmt_snippet, false, parse::ParseMode::Format);
184187

@@ -199,6 +202,7 @@ pub(crate) fn parse(
199202
let to_span = |inner_span: parse::InnerSpan| {
200203
is_source_literal.then(|| {
201204
TextRange::new(inner_span.start.try_into().unwrap(), inner_span.end.try_into().unwrap())
205+
- TextSize::from(str_style.map(|it| it + 1).unwrap_or(0) as u32 + 1)
202206
})
203207
};
204208

@@ -230,9 +234,10 @@ pub(crate) fn parse(
230234
Err(index)
231235
}
232236
}
233-
ArgRef::Name(name, _span) => {
237+
ArgRef::Name(name, span) => {
234238
let name = Name::new_text_dont_use(SmolStr::new(name));
235239
if let Some((index, _)) = args.by_name(&name) {
240+
record_usage(name, span);
236241
// Name found in `args`, so we resolve it to its index.
237242
if index < args.explicit_args().len() {
238243
// Mark it as used, if it was an explicit argument.
@@ -246,6 +251,7 @@ pub(crate) fn parse(
246251
// disabled (see RFC #2795)
247252
// FIXME: Diagnose
248253
}
254+
record_usage(name.clone(), span);
249255
Ok(args.add(FormatArgument {
250256
kind: FormatArgumentKind::Captured(name.clone()),
251257
// FIXME: This is problematic, we might want to synthesize a dummy

crates/hir/src/semantics.rs

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ use smallvec::{smallvec, SmallVec};
2929
use stdx::TupleExt;
3030
use syntax::{
3131
algo::skip_trivia_token,
32-
ast::{self, HasAttrs as _, HasGenericParams, HasLoopBody},
33-
match_ast, AstNode, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextSize,
32+
ast::{self, HasAttrs as _, HasGenericParams, HasLoopBody, IsString as _},
33+
match_ast, AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken,
34+
TextRange, TextSize,
3435
};
3536

3637
use crate::{
@@ -49,7 +50,7 @@ pub enum DescendPreference {
4950
None,
5051
}
5152

52-
#[derive(Debug, Clone, PartialEq, Eq)]
53+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
5354
pub enum PathResolution {
5455
/// An item
5556
Def(ModuleDef),
@@ -402,6 +403,41 @@ impl<'db> SemanticsImpl<'db> {
402403
)
403404
}
404405

406+
pub fn resolve_offset_in_format_args(
407+
&self,
408+
string: ast::String,
409+
offset: TextSize,
410+
) -> Option<(TextRange, Option<PathResolution>)> {
411+
debug_assert!(offset <= string.syntax().text_range().len());
412+
let literal = string.syntax().parent().filter(|it| it.kind() == SyntaxKind::LITERAL)?;
413+
let format_args = ast::FormatArgsExpr::cast(literal.parent()?)?;
414+
let source_analyzer = &self.analyze_no_infer(format_args.syntax())?;
415+
let format_args = self.wrap_node_infile(format_args);
416+
source_analyzer.resolve_offset_in_format_args(self.db, format_args.as_ref(), offset)
417+
}
418+
419+
pub fn check_for_format_args_template(
420+
&self,
421+
original_token: SyntaxToken,
422+
offset: TextSize,
423+
) -> Option<(TextRange, Option<PathResolution>)> {
424+
if let Some(original_string) = ast::String::cast(original_token.clone()) {
425+
if let Some(quote) = original_string.open_quote_text_range() {
426+
return self
427+
.descend_into_macros(DescendPreference::SameText, original_token.clone())
428+
.into_iter()
429+
.find_map(|token| {
430+
self.resolve_offset_in_format_args(
431+
ast::String::cast(token)?,
432+
offset - quote.end(),
433+
)
434+
})
435+
.map(|(range, res)| (range + quote.end(), res));
436+
}
437+
}
438+
None
439+
}
440+
405441
/// Maps a node down by mapping its first and last token down.
406442
pub fn descend_node_into_attributes<N: AstNode>(&self, node: N) -> SmallVec<[N; 1]> {
407443
// This might not be the correct way to do this, but it works for now
@@ -419,24 +455,27 @@ impl<'db> SemanticsImpl<'db> {
419455

420456
if first == last {
421457
// node is just the token, so descend the token
422-
self.descend_into_macros_impl(first, 0.into(), &mut |InFile { value, .. }| {
423-
if let Some(node) = value.parent_ancestors().find_map(N::cast) {
458+
self.descend_into_macros_impl(first, &mut |InFile { value, .. }| {
459+
if let Some(node) = value
460+
.parent_ancestors()
461+
.take_while(|it| it.text_range() == value.text_range())
462+
.find_map(N::cast)
463+
{
424464
res.push(node)
425465
}
426466
ControlFlow::Continue(())
427467
});
428468
} else {
429469
// Descend first and last token, then zip them to look for the node they belong to
430470
let mut scratch: SmallVec<[_; 1]> = smallvec![];
431-
self.descend_into_macros_impl(first, 0.into(), &mut |token| {
471+
self.descend_into_macros_impl(first, &mut |token| {
432472
scratch.push(token);
433473
ControlFlow::Continue(())
434474
});
435475

436476
let mut scratch = scratch.into_iter();
437477
self.descend_into_macros_impl(
438478
last,
439-
0.into(),
440479
&mut |InFile { value: last, file_id: last_fid }| {
441480
if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() {
442481
if first_fid == last_fid {
@@ -467,7 +506,6 @@ impl<'db> SemanticsImpl<'db> {
467506
&self,
468507
mode: DescendPreference,
469508
token: SyntaxToken,
470-
offset: TextSize,
471509
) -> SmallVec<[SyntaxToken; 1]> {
472510
enum Dp<'t> {
473511
SameText(&'t str),
@@ -487,7 +525,7 @@ impl<'db> SemanticsImpl<'db> {
487525
DescendPreference::None => Dp::None,
488526
};
489527
let mut res = smallvec![];
490-
self.descend_into_macros_impl(token.clone(), offset, &mut |InFile { value, .. }| {
528+
self.descend_into_macros_impl(token.clone(), &mut |InFile { value, .. }| {
491529
let is_a_match = match mode {
492530
Dp::SameText(text) => value.text() == text,
493531
Dp::SameKind(preferred_kind) => {
@@ -513,7 +551,6 @@ impl<'db> SemanticsImpl<'db> {
513551
&self,
514552
mode: DescendPreference,
515553
token: SyntaxToken,
516-
offset: TextSize,
517554
) -> SyntaxToken {
518555
enum Dp<'t> {
519556
SameText(&'t str),
@@ -533,7 +570,7 @@ impl<'db> SemanticsImpl<'db> {
533570
DescendPreference::None => Dp::None,
534571
};
535572
let mut res = token.clone();
536-
self.descend_into_macros_impl(token.clone(), offset, &mut |InFile { value, .. }| {
573+
self.descend_into_macros_impl(token.clone(), &mut |InFile { value, .. }| {
537574
let is_a_match = match mode {
538575
Dp::SameText(text) => value.text() == text,
539576
Dp::SameKind(preferred_kind) => {
@@ -558,9 +595,6 @@ impl<'db> SemanticsImpl<'db> {
558595
fn descend_into_macros_impl(
559596
&self,
560597
token: SyntaxToken,
561-
// FIXME: We might want this to be Option<TextSize> to be able to opt out of subrange
562-
// mapping, specifically for node downmapping
563-
_offset: TextSize,
564598
f: &mut dyn FnMut(InFile<SyntaxToken>) -> ControlFlow<()>,
565599
) {
566600
// FIXME: Clean this up
@@ -729,7 +763,7 @@ impl<'db> SemanticsImpl<'db> {
729763
offset: TextSize,
730764
) -> impl Iterator<Item = impl Iterator<Item = SyntaxNode> + '_> + '_ {
731765
node.token_at_offset(offset)
732-
.map(move |token| self.descend_into_macros(DescendPreference::None, token, offset))
766+
.map(move |token| self.descend_into_macros(DescendPreference::None, token))
733767
.map(|descendants| {
734768
descendants.into_iter().map(move |it| self.token_ancestors_with_macros(it))
735769
})

crates/hir/src/source_analyzer.rs

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,29 @@ impl SourceAnalyzer {
820820
false
821821
}
822822

823+
pub(crate) fn resolve_offset_in_format_args(
824+
&self,
825+
db: &dyn HirDatabase,
826+
format_args: InFile<&ast::FormatArgsExpr>,
827+
offset: TextSize,
828+
) -> Option<(TextRange, Option<PathResolution>)> {
829+
let implicits = self.body_source_map()?.implicit_format_args(format_args)?;
830+
implicits.iter().find(|(range, _)| range.contains_inclusive(offset)).map(|(range, name)| {
831+
(
832+
*range,
833+
resolve_hir_value_path(
834+
db,
835+
&self.resolver,
836+
self.resolver.body_owner(),
837+
&Path::from_known_path_with_no_generic(ModPath::from_segments(
838+
PathKind::Plain,
839+
Some(name.clone()),
840+
)),
841+
),
842+
)
843+
})
844+
}
845+
823846
fn resolve_impl_method_or_trait_def(
824847
&self,
825848
db: &dyn HirDatabase,
@@ -1038,24 +1061,7 @@ fn resolve_hir_path_(
10381061
};
10391062

10401063
let body_owner = resolver.body_owner();
1041-
let values = || {
1042-
resolver.resolve_path_in_value_ns_fully(db.upcast(), path).and_then(|val| {
1043-
let res = match val {
1044-
ValueNs::LocalBinding(binding_id) => {
1045-
let var = Local { parent: body_owner?, binding_id };
1046-
PathResolution::Local(var)
1047-
}
1048-
ValueNs::FunctionId(it) => PathResolution::Def(Function::from(it).into()),
1049-
ValueNs::ConstId(it) => PathResolution::Def(Const::from(it).into()),
1050-
ValueNs::StaticId(it) => PathResolution::Def(Static::from(it).into()),
1051-
ValueNs::StructId(it) => PathResolution::Def(Struct::from(it).into()),
1052-
ValueNs::EnumVariantId(it) => PathResolution::Def(Variant::from(it).into()),
1053-
ValueNs::ImplSelf(impl_id) => PathResolution::SelfType(impl_id.into()),
1054-
ValueNs::GenericParam(id) => PathResolution::ConstParam(id.into()),
1055-
};
1056-
Some(res)
1057-
})
1058-
};
1064+
let values = || resolve_hir_value_path(db, resolver, body_owner, path);
10591065

10601066
let items = || {
10611067
resolver
@@ -1075,6 +1081,30 @@ fn resolve_hir_path_(
10751081
.or_else(macros)
10761082
}
10771083

1084+
fn resolve_hir_value_path(
1085+
db: &dyn HirDatabase,
1086+
resolver: &Resolver,
1087+
body_owner: Option<DefWithBodyId>,
1088+
path: &Path,
1089+
) -> Option<PathResolution> {
1090+
resolver.resolve_path_in_value_ns_fully(db.upcast(), path).and_then(|val| {
1091+
let res = match val {
1092+
ValueNs::LocalBinding(binding_id) => {
1093+
let var = Local { parent: body_owner?, binding_id };
1094+
PathResolution::Local(var)
1095+
}
1096+
ValueNs::FunctionId(it) => PathResolution::Def(Function::from(it).into()),
1097+
ValueNs::ConstId(it) => PathResolution::Def(Const::from(it).into()),
1098+
ValueNs::StaticId(it) => PathResolution::Def(Static::from(it).into()),
1099+
ValueNs::StructId(it) => PathResolution::Def(Struct::from(it).into()),
1100+
ValueNs::EnumVariantId(it) => PathResolution::Def(Variant::from(it).into()),
1101+
ValueNs::ImplSelf(impl_id) => PathResolution::SelfType(impl_id.into()),
1102+
ValueNs::GenericParam(id) => PathResolution::ConstParam(id.into()),
1103+
};
1104+
Some(res)
1105+
})
1106+
}
1107+
10781108
/// Resolves a path where we know it is a qualifier of another path.
10791109
///
10801110
/// For example, if we have:

0 commit comments

Comments
 (0)