1
Fork 0

Rollup merge of #139014 - xizheyin:issue-138931, r=oli-obk

Improve suggest construct with literal syntax instead of calling

Closing #138931

When constructing a structure through a format similar to calling a constructor, we can use verbose suggestions to hint at using literal syntax for clearer advice. The case of multiple fields is also considered here, provided that the field has the same number of arguments as CallExpr.

r? compiler
This commit is contained in:
Jacob Pratt 2025-03-27 13:11:20 -04:00 committed by GitHub
commit d517a4f0ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 156 additions and 33 deletions

View file

@ -1665,41 +1665,81 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
// the struct literal syntax at all, as that will cause a subsequent error.
let fields = this.r.field_idents(def_id);
let has_fields = fields.as_ref().is_some_and(|f| !f.is_empty());
let (fields, applicability) = match fields {
Some(fields) => {
let fields = if let Some(old_fields) = old_fields {
fields
.iter()
.enumerate()
.map(|(idx, new)| (new, old_fields.get(idx)))
.map(|(new, old)| {
if let Some(Some(old)) = old
&& new.as_str() != old
{
format!("{new}: {old}")
} else {
new.to_string()
}
})
.collect::<Vec<String>>()
} else {
fields
.iter()
.map(|f| format!("{f}{tail}"))
.collect::<Vec<String>>()
};
(fields.join(", "), applicability)
}
None => ("/* fields */".to_string(), Applicability::HasPlaceholders),
};
let pad = if has_fields { " " } else { "" };
err.span_suggestion(
if let PathSource::Expr(Some(Expr {
kind: ExprKind::Call(path, args),
span,
format!("use struct {descr} syntax instead"),
format!("{path_str} {{{pad}{fields}{pad}}}"),
applicability,
);
..
})) = source
&& !args.is_empty()
&& let Some(fields) = &fields
&& args.len() == fields.len()
// Make sure we have same number of args as fields
{
let path_span = path.span;
let mut parts = Vec::new();
// Start with the opening brace
parts.push((
path_span.shrink_to_hi().until(args[0].span),
"{".to_owned(),
));
for (field, arg) in fields.iter().zip(args.iter()) {
// Add the field name before the argument
parts.push((arg.span.shrink_to_lo(), format!("{}: ", field)));
}
// Add the closing brace
parts.push((
args.last().unwrap().span.shrink_to_hi().until(span.shrink_to_hi()),
"}".to_owned(),
));
err.multipart_suggestion_verbose(
format!("use struct {descr} syntax instead of calling"),
parts,
applicability,
);
} else {
let (fields, applicability) = match fields {
Some(fields) => {
let fields = if let Some(old_fields) = old_fields {
fields
.iter()
.enumerate()
.map(|(idx, new)| (new, old_fields.get(idx)))
.map(|(new, old)| {
if let Some(Some(old)) = old
&& new.as_str() != old
{
format!("{new}: {old}")
} else {
new.to_string()
}
})
.collect::<Vec<String>>()
} else {
fields
.iter()
.map(|f| format!("{f}{tail}"))
.collect::<Vec<String>>()
};
(fields.join(", "), applicability)
}
None => {
("/* fields */".to_string(), Applicability::HasPlaceholders)
}
};
let pad = if has_fields { " " } else { "" };
err.span_suggestion(
span,
format!("use struct {descr} syntax instead"),
format!("{path_str} {{{pad}{fields}{pad}}}"),
applicability,
);
}
}
if let PathSource::Expr(Some(Expr {
kind: ExprKind::Call(path, args),

View file

@ -0,0 +1,25 @@
struct PersonOnlyName {
name: String
}
struct PersonWithAge {
name: String,
age: u8,
height: u8,
}
fn main() {
let wilfred = PersonOnlyName("Name1".to_owned());
//~^ ERROR expected function, tuple struct or tuple variant, found struct `PersonOnlyName` [E0423]
let bill = PersonWithAge( //~ ERROR expected function, tuple struct or tuple variant, found struct `PersonWithAge` [E0423]
"Name2".to_owned(),
20,
180,
);
let person = PersonWithAge("Name3".to_owned());
//~^ ERROR expected function, tuple struct or tuple variant, found struct `PersonWithAge` [E0423]
}

View file

@ -0,0 +1,58 @@
error[E0423]: expected function, tuple struct or tuple variant, found struct `PersonOnlyName`
--> $DIR/struct-construct-with-call-issue-138931.rs:14:19
|
LL | / struct PersonOnlyName {
LL | | name: String
LL | | }
| |_- `PersonOnlyName` defined here
...
LL | let wilfred = PersonOnlyName("Name1".to_owned());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: use struct literal syntax instead of calling
|
LL - let wilfred = PersonOnlyName("Name1".to_owned());
LL + let wilfred = PersonOnlyName{name: "Name1".to_owned()};
|
error[E0423]: expected function, tuple struct or tuple variant, found struct `PersonWithAge`
--> $DIR/struct-construct-with-call-issue-138931.rs:17:16
|
LL | / struct PersonWithAge {
LL | | name: String,
LL | | age: u8,
LL | | height: u8,
LL | | }
| |_- `PersonWithAge` defined here
...
LL | let bill = PersonWithAge(
| ________________^
LL | | "Name2".to_owned(),
LL | | 20,
LL | | 180,
LL | | );
| |_____^
|
help: use struct literal syntax instead of calling
|
LL ~ let bill = PersonWithAge{name: "Name2".to_owned(),
LL ~ age: 20,
LL ~ height: 180};
|
error[E0423]: expected function, tuple struct or tuple variant, found struct `PersonWithAge`
--> $DIR/struct-construct-with-call-issue-138931.rs:23:18
|
LL | / struct PersonWithAge {
LL | | name: String,
LL | | age: u8,
LL | | height: u8,
LL | | }
| |_- `PersonWithAge` defined here
...
LL | let person = PersonWithAge("Name3".to_owned());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use struct literal syntax instead: `PersonWithAge { name: val, age: val, height: val }`
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0423`.