Skip to content

Commit fdc297f

Browse files
committed
Malformed call function call: do multiple suggestions when possible
1 parent 16ad385 commit fdc297f

File tree

7 files changed

+188
-150
lines changed

7 files changed

+188
-150
lines changed

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

Lines changed: 136 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,10 +1179,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11791179
let mut only_extras_so_far = errors
11801180
.peek()
11811181
.is_some_and(|first| matches!(first, Error::Extra(arg_idx) if arg_idx.index() == 0));
1182+
let mut only_missing_so_far = true;
11821183
let mut prev_extra_idx = None;
11831184
let mut suggestions = vec![];
11841185
while let Some(error) = errors.next() {
11851186
only_extras_so_far &= matches!(error, Error::Extra(_));
1187+
only_missing_so_far &= matches!(error, Error::Missing(_));
11861188

11871189
match error {
11881190
Error::Invalid(provided_idx, expected_idx, compatibility) => {
@@ -1580,80 +1582,146 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
15801582
&& !full_call_span.in_external_macro(self.sess().source_map())
15811583
{
15821584
let source_map = self.sess().source_map();
1583-
let suggestion_span = if let Some(args_span) = error_span.trim_start(full_call_span) {
1584-
// Span of the braces, e.g. `(a, b, c)`.
1585-
args_span
1586-
} else {
1587-
// The arg span of a function call that wasn't even given braces
1588-
// like what might happen with delegation reuse.
1589-
// e.g. `reuse HasSelf::method;` should suggest `reuse HasSelf::method($args);`.
1590-
full_call_span.shrink_to_hi()
1591-
};
1592-
1593-
// Controls how the arguments should be listed in the suggestion.
1594-
enum ArgumentsFormatting {
1595-
SingleLine,
1596-
Multiline { fallback_indent: String, brace_indent: String },
1597-
}
1598-
let arguments_formatting = {
1599-
let mut provided_inputs = matched_inputs.iter().filter_map(|a| *a);
1600-
if let Some(brace_indent) = source_map.indentation_before(suggestion_span)
1601-
&& let Some(first_idx) = provided_inputs.by_ref().next()
1602-
&& let Some(last_idx) = provided_inputs.by_ref().next()
1603-
&& let (_, first_span) = provided_arg_tys[first_idx]
1604-
&& let (_, last_span) = provided_arg_tys[last_idx]
1605-
&& source_map.is_multiline(first_span.to(last_span))
1606-
&& let Some(fallback_indent) = source_map.indentation_before(first_span)
1607-
{
1608-
ArgumentsFormatting::Multiline { fallback_indent, brace_indent }
1585+
let (suggestion_span, has_paren) =
1586+
if let Some(args_span) = error_span.trim_start(full_call_span) {
1587+
// Span of the braces, e.g. `(a, b, c)`.
1588+
(args_span, true)
16091589
} else {
1610-
ArgumentsFormatting::SingleLine
1611-
}
1612-
};
1590+
// The arg span of a function call that wasn't even given braces
1591+
// like what might happen with delegation reuse.
1592+
// e.g. `reuse HasSelf::method;` should suggest `reuse HasSelf::method($args);`.
1593+
(full_call_span.shrink_to_hi(), false)
1594+
};
16131595

1614-
let mut suggestion = "(".to_owned();
1615-
let mut needs_comma = false;
1616-
for (expected_idx, provided_idx) in matched_inputs.iter_enumerated() {
1617-
if needs_comma {
1618-
suggestion += ",";
1619-
}
1620-
match &arguments_formatting {
1621-
ArgumentsFormatting::SingleLine if needs_comma => suggestion += " ",
1622-
ArgumentsFormatting::SingleLine => {}
1623-
ArgumentsFormatting::Multiline { .. } => suggestion += "\n",
1596+
if has_paren && only_missing_so_far {
1597+
// Controls how the arguments should be listed in the suggestion.
1598+
enum ArgumentsFormatting {
1599+
SingleLine,
1600+
Multiline { fallback_indent: String },
16241601
}
1625-
needs_comma = true;
1626-
let (suggestion_span, suggestion_text) = if let Some(provided_idx) = provided_idx
1627-
&& let (_, provided_span) = provided_arg_tys[*provided_idx]
1628-
&& let Ok(arg_text) = source_map.span_to_snippet(provided_span)
1629-
{
1630-
(Some(provided_span), arg_text)
1631-
} else {
1632-
// Propose a placeholder of the correct type
1633-
let (_, expected_ty) = formal_and_expected_inputs[expected_idx];
1634-
(None, ty_to_snippet(expected_ty, expected_idx))
1602+
let arguments_formatting = {
1603+
let mut provided_inputs = matched_inputs.iter().filter_map(|a| *a);
1604+
if let Some(first_idx) = provided_inputs.by_ref().next()
1605+
&& let Some(last_idx) = provided_inputs.by_ref().next()
1606+
&& let (_, first_span) = provided_arg_tys[first_idx]
1607+
&& let (_, last_span) = provided_arg_tys[last_idx]
1608+
&& source_map.is_multiline(first_span.to(last_span))
1609+
&& let Some(fallback_indent) = source_map.indentation_before(first_span)
1610+
{
1611+
ArgumentsFormatting::Multiline { fallback_indent }
1612+
} else {
1613+
ArgumentsFormatting::SingleLine
1614+
}
16351615
};
1636-
if let ArgumentsFormatting::Multiline { fallback_indent, .. } =
1637-
&arguments_formatting
1638-
{
1639-
let indent = suggestion_span
1640-
.and_then(|span| source_map.indentation_before(span))
1641-
.unwrap_or_else(|| fallback_indent.clone());
1642-
suggestion += &indent;
1616+
1617+
let mut suggestions: Vec<(Span, String)> = Vec::new();
1618+
let mut previous_span = source_map.start_point(suggestion_span).shrink_to_hi();
1619+
let mut no_provided_arg_yet = true;
1620+
for (expected_idx, provided_idx) in matched_inputs.iter_enumerated() {
1621+
if let Some(provided_idx) = provided_idx {
1622+
let (_, provided_span) = provided_arg_tys[*provided_idx];
1623+
previous_span = provided_span;
1624+
no_provided_arg_yet = false;
1625+
} else {
1626+
// Propose a placeholder of the correct type
1627+
let (insertion_span, last_arg) = if no_provided_arg_yet {
1628+
(previous_span, matched_inputs.len() - 1 == expected_idx.as_usize())
1629+
} else {
1630+
let mut comma_hit = false;
1631+
let mut closing_paren_hit = false;
1632+
let after_arg = previous_span.shrink_to_hi();
1633+
let after_previous_comma = source_map
1634+
.span_extend_while(after_arg, |c| {
1635+
closing_paren_hit = c == ')';
1636+
if comma_hit || closing_paren_hit {
1637+
false
1638+
} else {
1639+
comma_hit = c == ',';
1640+
true
1641+
}
1642+
})
1643+
.unwrap()
1644+
.shrink_to_hi();
1645+
let span = if closing_paren_hit {
1646+
after_previous_comma
1647+
} else {
1648+
source_map.next_point(after_previous_comma).shrink_to_hi()
1649+
};
1650+
(span, closing_paren_hit)
1651+
};
1652+
let (_, expected_ty) = formal_and_expected_inputs[expected_idx];
1653+
let expected_ty = ty_to_snippet(expected_ty, expected_idx);
1654+
let indent = match arguments_formatting {
1655+
ArgumentsFormatting::SingleLine => "",
1656+
ArgumentsFormatting::Multiline { ref fallback_indent, .. } => {
1657+
fallback_indent
1658+
}
1659+
};
1660+
let prefix = if last_arg && !no_provided_arg_yet {
1661+
// `call(a)` -> `call(a, b)` requires adding a comma.
1662+
", "
1663+
} else {
1664+
""
1665+
};
1666+
let suffix = match arguments_formatting {
1667+
ArgumentsFormatting::SingleLine if !last_arg => ", ",
1668+
ArgumentsFormatting::SingleLine => "",
1669+
ArgumentsFormatting::Multiline { .. } => "\n",
1670+
};
1671+
let suggestion = format!("{indent}{prefix}{expected_ty}{suffix}");
1672+
if let Some((last_sugg_span, last_sugg_msg)) = suggestions.last_mut()
1673+
&& *last_sugg_span == insertion_span
1674+
{
1675+
// More than one suggestion to insert at a given span.
1676+
// Merge them into one.
1677+
if last_sugg_msg.ends_with(", ") && prefix == ", " {
1678+
// TODO: explain what this condition is and why
1679+
// it is needed.
1680+
// TODO: find a better way to express this
1681+
// condition.
1682+
last_sugg_msg.truncate(last_sugg_msg.len() - 2);
1683+
}
1684+
last_sugg_msg.push_str(&suggestion);
1685+
} else {
1686+
suggestions.push((insertion_span, suggestion));
1687+
};
1688+
};
16431689
}
1644-
suggestion += &suggestion_text;
1645-
}
1646-
if let ArgumentsFormatting::Multiline { brace_indent, .. } = arguments_formatting {
1647-
suggestion += ",\n";
1648-
suggestion += &brace_indent;
1690+
err.multipart_suggestion_verbose(
1691+
suggestion_text,
1692+
suggestions,
1693+
Applicability::HasPlaceholders,
1694+
);
1695+
} else {
1696+
// FIXME: make this multiline-aware.
1697+
let mut suggestion = "(".to_owned();
1698+
let mut needs_comma = false;
1699+
for (expected_idx, provided_idx) in matched_inputs.iter_enumerated() {
1700+
if needs_comma {
1701+
suggestion += ", ";
1702+
} else {
1703+
needs_comma = true;
1704+
}
1705+
let suggestion_text = if let Some(provided_idx) = provided_idx
1706+
&& let (_, provided_span) = provided_arg_tys[*provided_idx]
1707+
&& let Ok(arg_text) = source_map.span_to_snippet(provided_span)
1708+
{
1709+
arg_text
1710+
} else {
1711+
// Propose a placeholder of the correct type
1712+
let (_, expected_ty) = formal_and_expected_inputs[expected_idx];
1713+
ty_to_snippet(expected_ty, expected_idx)
1714+
};
1715+
suggestion += &suggestion_text;
1716+
}
1717+
suggestion += ")";
1718+
err.span_suggestion_verbose(
1719+
suggestion_span,
1720+
suggestion_text,
1721+
suggestion,
1722+
Applicability::HasPlaceholders,
1723+
);
16491724
}
1650-
suggestion += ")";
1651-
err.span_suggestion_verbose(
1652-
suggestion_span,
1653-
suggestion_text,
1654-
suggestion,
1655-
Applicability::HasPlaceholders,
1656-
);
16571725
}
16581726

16591727
err.emit()

compiler/rustc_span/src/source_map.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -543,9 +543,13 @@ impl SourceMap {
543543
/// Extracts the source surrounding the given `Span` using the `extract_source` function. The
544544
/// extract function takes three arguments: a string slice containing the source, an index in
545545
/// the slice for the beginning of the span and an index in the slice for the end of the span.
546-
pub fn span_to_source<F, T>(&self, sp: Span, extract_source: F) -> Result<T, SpanSnippetError>
546+
pub fn span_to_source<F, T>(
547+
&self,
548+
sp: Span,
549+
mut extract_source: F,
550+
) -> Result<T, SpanSnippetError>
547551
where
548-
F: Fn(&str, usize, usize) -> Result<T, SpanSnippetError>,
552+
F: FnMut(&str, usize, usize) -> Result<T, SpanSnippetError>,
549553
{
550554
let local_begin = self.lookup_byte_offset(sp.lo());
551555
let local_end = self.lookup_byte_offset(sp.hi());
@@ -700,7 +704,7 @@ impl SourceMap {
700704
pub fn span_extend_while(
701705
&self,
702706
span: Span,
703-
f: impl Fn(char) -> bool,
707+
mut f: impl FnMut(char) -> bool,
704708
) -> Result<Span, SpanSnippetError> {
705709
self.span_to_source(span, |s, _start, end| {
706710
let n = s[end..].char_indices().find(|&(_, c)| !f(c)).map_or(s.len() - end, |(i, _)| i);

tests/ui/argument-suggestions/issue-100478.stderr

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@ LL | fn three_diff(_a: T1, _b: T2, _c: T3) {}
1414
| ^^^^^^^^^^ ------ ------
1515
help: provide the arguments
1616
|
17-
LL - three_diff(T2::new(0));
18-
LL + three_diff(/* T1 */, T2::new(0), /* T3 */);
19-
|
17+
LL | three_diff(/* T1 */, T2::new(0), /* T3 */);
18+
| +++++++++ ++++++++++
2019

2120
error[E0308]: arguments to this function are incorrect
2221
--> $DIR/issue-100478.rs:35:5
@@ -75,17 +74,8 @@ LL | fn foo(p1: T1, p2: Arc<T2>, p3: T3, p4: Arc<T4>, p5: T5, p6: T6, p7: T7, p8
7574
| ^^^ -----------
7675
help: provide the argument
7776
|
78-
LL ~ foo(
79-
LL + p1,
80-
LL + /* Arc<T2> */,
81-
LL + p3,
82-
LL + p4,
83-
LL + p5,
84-
LL + p6,
85-
LL + p7,
86-
LL + p8,
87-
LL ~ );
88-
|
77+
LL | p1, /* Arc<T2> */
78+
| +++++++++++++
8979

9080
error: aborting due to 4 previous errors
9181

0 commit comments

Comments
 (0)