Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve suggest construct with literal syntax instead of calling #139014

Merged
merged 2 commits into from
Mar 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 74 additions & 34 deletions compiler/rustc_resolve/src/late/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1681,41 +1681,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),
Expand Down
25 changes: 25 additions & 0 deletions tests/ui/structs/struct-construct-with-call-issue-138931.rs
Original file line number Diff line number Diff line change
@@ -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]
}
58 changes: 58 additions & 0 deletions tests/ui/structs/struct-construct-with-call-issue-138931.stderr
Original file line number Diff line number Diff line change
@@ -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`.
Loading