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

Better method call error messages #71827

Closed

Conversation

Quantumplation
Copy link
Contributor

Summary

This attempts to bring better error messages to invalid method calls, by applying some heuristics to identify common mistakes. Closes #65853.

So, as just one example, instead of:

error[E0061]: this function takes 2 arguments but 3 arguments were supplied
  --> src/main.rs:12:5
   |
10 | fn vec2D(_x: X, _y: Y) {}
   | ---------------------- defined here
11 | fn main() {
12 |     vec2D(X {}, Y {}, Z {});
   |     ^^^^^ ----  ----  ---- supplied 3 arguments
   |     |
   |     expected 2 arguments

We can provide something along the lines of

error[E0059]: one or more arguments to this function are incorrect
  --> /home/vsonline/workspace/rust/src/test/ui/argument-suggestions/basic.rs:11:5
   |
LL |     vec2D(X {}, Y {}, Z {});
   |     ^^^^^^^^^^^^^^^^^^----^
   |                       |
   |                       this argument appears to be extra

That being said, the error messages themselves are what I'm seeking the most feedback on

Terminology

The thing that will probably help the most when reading this code is being strict about the following terms:

  • "inputs" refers to the inputs being expected by the function,
  • "arguments" refers to what has been provided at the call site

Algorithm

Overview

The algorithm is inspired by Levenshtein distance and longest common sub-sequence. In essence, we treat the types of the function, and the types of the arguments you provided as two "words" and compute the edits to get from one to the other.

We then extend that algorithm to distinguish 5 cases:

  • A function input is missing
  • An extra argument was provided
  • The type of an argument is straight up invalid
  • Two arguments have been swapped
  • A subset of the arguments have been shuffled

(We detect the last two as separate cases so that we can detect two swaps, instead of 4 parameters permuted.)

Details

The basic sketch of the algorithm is as follows:

  • Construct a boolean grid, with a row for each argument, and a column for each input. The cell [i, j] is true if the i'th argument could satisfy the j'th input.
  • If we find an argument that could satisfy no inputs, provided for an input that can't be satisfied by any other argument, we consider this an "invalid type".
  • Extra arguments are those that can't satisfy any input, provided for an input that could be satisfied by another argument.
  • Missing inputs are inputs that can't be satisfied by any argument, where the provided argument could satisfy another input
  • Swapped / Permuted arguments are identified with a cycle detection algorithm.

As each issue is found, we remove the relevant inputs / arguments and check for more issues. If we find no issues, we match up any "valid" arguments, and start again.

Even more details

Note that there's a lot of extra complexity:

  • We try to stay efficient on the happy path, only computing the diagonal until we find a problem, and then filling in the rest of the matrix.
  • Closure arguments are wrapped in a tuple and need to be unwrapped
  • We need to resolve closure types after the rest, to allow the most specific type constraints
  • We need to handle imported C functions that might be variadic in their inputs.

I tried to document a lot of this in comments in the code and keep the naming clear.

Remaining work for this PR

  • Improve the diagnostics A LOT. The complex case from the test I added in particular shows how noisy it can get
  • A few small FIXMEs
  • I can probably improve the performance further by not allocating the compatibility_matrix until call_appears_satisfied is false, and only use a single vector for the "diagonal" copying this into the CM
  • I'm rather new to rust, so some of this may be un-idiomatic, and I'm open to suggestions
  • Similarly, if anything has existing implementations (or should be implemented elsewhere), I'm happy to switch over to those, I'm just unfamiliar with what's available.
  • I was also considering pulling this logic out into it's own file, since it's getting more nuanced, but I wanted to see what other people thought first
  • Blessing / reviewing other tests (didn't want to do this until the diagnostics were finalized)
  • Lots more test cases (and potentially centralizing some existing tests?)
  • Any other feedback welcome!

Potential extensions

  • Extend the notion of "compatibility" to be tolerant to borrow and mutability
  • Feed our best guess for argument input assignment into the types table, to help things like trait inference (i.e. if you leave off an argument, don't let that screw up trait inference for everything after it)

r? @estebank since he helped me a lot with this

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label May 3, 2020
@Quantumplation
Copy link
Contributor Author

(is there a way to avoid clogging up the build pipeline until I've gathered and responded to feedback? I don't want to waste other people's time)

@Quantumplation
Copy link
Contributor Author

Also, if anyones curious, here's the scratch pad I used to iterate on the algorithm, and all the test cases I tested it on:

https://glot.io/snippets/fmxqlb8dct

The algorithm in rust shifted slightly during development, but I'll probably be adding each of the cases at the bottom to the tests for this PR.

Copy link
Contributor

@estebank estebank left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the direction this is going.

I'm only looking at the output and will review the code later, but one preliminary comment would be to split the logic into multiple methods/functions to make it easier to follow. Left some comments about the output. For the structured suggestions (help blogs) you can use the multipart_suggestion method.

Also, for the cases where the problem is type parameter permutation we shouldn't be using the same error code.

Comment on lines 1 to 7
error[E0059]: one or more arguments to this function are incorrect
--> $DIR/basic.rs:31:5
|
LL | invalid(1.0);
| ^^^^^^^^---^
| |
| the type of this argument is incorrect
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current output is better:

error[E0308]: mismatched types
  --> asdf.rs:18:13
   |
18 |     invalid(1.0); //~ ERROR arguments to this function are incorrect
   |             ^^^ expected `u32`, found floating-point number

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I wasn't quite sure how to get human readable names for the error message, and when I tried looking into the existing code it became a very deep rabbit hole and I quickly got lost. Any tips on methods I could reuse to avoid a ton of spurious work?

Comment on lines 9 to 15
error[E0059]: one or more arguments to this function are incorrect
--> $DIR/basic.rs:32:5
|
LL | extra(&"");
| ^^^^^^---^
| |
| this argument appears to be extra
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to keep the current output for this case:

error[E0061]: this function takes 0 arguments but 1 argument was supplied
  --> asdf.rs:19:5
   |
11 | fn extra() {}
   | ---------- defined here
...
19 |     extra(&""); //~ ERROR arguments to this function are incorrect
   |     ^^^^^ --- supplied 1 argument
   |     |
   |     expected 0 arguments

For this case we can use a span_suggestion_hidden that will only appear to rustfix and rust-analyzer for the removal of the argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two main questions that would help me get better diagnostics:

  • Should we try to blanket these all under one error, with sub-diagnostics for each case, or split things out into separate errors to more closely match "existing" outputs?

  • How should we handle cases where there are multiple instances of an issue? Should we highlight each separately, or aggregate them together?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can have multiple .span_note(vec![spans], "message") for each of the cases you handle.

Comment on lines 17 to 23
error[E0059]: one or more arguments to this function are incorrect
--> $DIR/basic.rs:33:5
|
LL | missing();
| ^^^^^^^^^
|
= note: the 1st parameter appears to be missing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error[E0061]: this function takes 1 argument but 0 arguments were supplied
  --> asdf.rs:20:5
   |
12 | fn missing(_i: u32) {}
   | ------------------- defined here
...
20 |     missing(); //~ ERROR arguments to this function are incorrect
   |     ^^^^^^^-- supplied 0 arguments
   |     |
   |     expected 1 argument of type `u32`

I would like to keep the current output. A possibility of improvement would be to look for local bindings that are of the appropriate type (but that doesn't need to be addressed in this PR).

Comment on lines 25 to 32
error[E0059]: one or more arguments to this function are incorrect
--> $DIR/basic.rs:34:5
|
LL | swapped(&"", 1);
| ^^^^^^^^---^^-^
| | |
| | these two arguments appear to be swapped
| these two arguments appear to be swapped
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer this to be closer to

error[E0059]: multiple arguments to this function are incorrect
  --> $DIR/basic.rs:34:5
   |
LL |     swapped(&"", 1);
   |             ^^^  ^ expected `&str`, found `{integer}`
   |             |
   |             expected `i32`, found `&str`
help: the arguments can be reordered to be of the appropriate types in the right positions
   |
LL |     swapped(1, &"");
   |             ^  ^^^

Comment on lines 34 to 42
error[E0059]: one or more arguments to this function are incorrect
--> $DIR/basic.rs:35:5
|
LL | permuted(Y {}, Z {}, X {});
| ^^^^^^^^^----^^----^^----^
| | | |
| | | these 3 arguments appear to be in the wrong order
| | these 3 arguments appear to be in the wrong order
| these 3 arguments appear to be in the wrong order
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Comment on lines 44 to 58
error[E0059]: one or more arguments to this function are incorrect
--> $DIR/basic.rs:36:5
|
LL | complex(1.0, H {}, &"", G{}, F::X2, Z {}, X {}, Y {});
| ^^^^^^^^---^^----^^^^^^^---^^-----^^----^^----^^----^
| | | | | | | |
| | | | | | | these 3 arguments appear to be in the wrong order
| | | | | | these 3 arguments appear to be in the wrong order
| | | | | these 3 arguments appear to be in the wrong order
| | | | these two arguments appear to be swapped
| | | these two arguments appear to be swapped
| | this argument appears to be extra
| the type of this argument is incorrect
|
= note: the 3rd parameter appears to be missing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about something like the following?

error[E0059]: multiple arguments to this function are incorrect
  --> $DIR/basic.rs:36:5
   |
   |   fn complex(_i: u32, _s: &str, _e: E, _f: F, _g: G, _x: X, _y: Y, _z: Z ) {}
   |      ------- function defined here
   |
...
LL |     complex(1.0, H {}, &"", G{}, F::X2, Z {}, X {}, Y {});
   |             ^^^  ^^^^      ^^^^  ^^^^^  ^^^^  ^^^^  ^^^^
   |             |    |         ||    |      |     |     |
   |             |    |         ||    |      |     |     expected `Z`, found `Y`
   |             |    |         ||    |      |     expected `Y`, found `X`
   |             |    |         ||    |      expected `X`, found `Z`
   |             |    |         ||    expected `G`, found `F`
   |             |    |         |expected `F`, found `G`
   |             |    |         missing argument of type `E`
   |             |    no parameter of type `H` is needed in `complex`
   |             expected `u32`, found `{float}`
help: the arguments can be modified to be of the appropriate types in the right positions
   |
LL |     complex(<u32>, &"", <E>, F::X2, G{}, X {}, Y {}, Z {});
   |             ^^^^^ --    ^^^^^^^^^^  ^^^  ^^^^  ^^^^  ^^^^
   |

For the float literal we could also use the helper method to suggest changing it to an integer, but first lets get the general case down to the final output.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hand rolled some code to construct the diagnostic for this specific case, hard coding in values/assumptions, to get a feel for the methods I might need to use, and was able to produce:

------------------------------------------
error[E0059]: multiple arguments to this function are incorrect
  --> /home/vsonline/workspace/rust/src/test/ui/argument-suggestions/basic.rs:12:13
   |
LL | fn complex(_i: u32, _s: &str, _e: E, _f: F, _g: G, _x: X, _y: Y, _z: Z ) {}
   |    ------- function defined here
...
LL |     complex(1.0, H {}, &"", G{}, F::X2, Z {}, X {}, Y {});
   |             ^^^  ^^^^      ^^^^  ^^^^^  ^^^^  ^^^^  ^^^^ expected `Z`, found `Y`
   |             |    |         ||    |      |     |
   |             |    |         ||    |      |     expected `Y`, found `X`
   |             |    |         ||    |      expected `X`, found `Z`
   |             |    |         ||    expected `G`, found `F`
   |             |    |         |expected `F`, found `G`
   |             |    |         missing argument of type `E`
   |             |    no parameter of type `H` is needed in `complex`
   |             expected `u32`, found `{float}`
   |
help: the arguments can be modified to be of the appropriate types in the right positions
   |
LL |     complex(<u32>, &"",<E>,  F::X2, G {}, X {}, Y {}, Z {});
   |             ^^^^^ --   ^^^^  ^^^^^  ^^^^  ^^^^  ^^^^  ^^^^

error: aborting due to previous error

which is pretty close to what you had.

Here's the code that produces that:

            if issues.len() > 0 {
                // Try to construct a diagnostic that best summarizes the issues we found
                // In general, if we found many issues, we'll emit a large diagnostic with
                // a multi-part suggestion
                let spans = vec![
                    provided_args[0].span,
                    provided_args[1].span,
                    //provided_args[3].span.shrink_to_lo(),
                    Span::new(BytePos(provided_args[3].span.lo().to_u32() - 1u32), provided_args[3].span.lo(), provided_args[3].span.ctxt()).shrink_to_lo(),
                    provided_args[3].span,
                    provided_args[4].span,
                    provided_args[5].span,
                    provided_args[6].span,
                    provided_args[7].span,
                ];
                let highlight_sp = MultiSpan::from_spans(spans.clone());
                let mut err = struct_span_err!(
                    tcx.sess,
                    highlight_sp,
                    E0059, // FIXME: Choose a different code
                    "multiple arguments to this function are incorrect"
                );
                if let Some(fn_def_span) = _fn_def_span {
                    let fn_name = Span::new(BytePos(fn_def_span.lo().to_u32() + 3u32), BytePos(fn_def_span.lo().to_u32() + 10u32), fn_def_span.ctxt());
                    err.span_label(fn_name, "function defined here");
                }
                err.span_label(spans[0], "expected `u32`, found `{float}`");
                err.span_label(spans[1], "no parameter of type `H` is needed in `complex`");
                err.span_label(spans[2], "missing argument of type `E`");
                err.span_label(spans[3], "expected `F`, found `G`");
                err.span_label(spans[4], "expected `G`, found `F`");
                err.span_label(spans[5], "expected `X`, found `Z`");
                err.span_label(spans[6], "expected `Y`, found `X`");
                err.span_label(spans[7], "expected `Z`, found `Y`");

                err.multipart_suggestion(
                    "the arguments can be modified to be of the appropriate types in the right positions",
                    vec![
                        (spans[0], "<u32>".to_string()),
                        (Span::new(spans[1].lo(), BytePos(spans[1].hi().to_u32() + 2u32), spans[1].ctxt()), "".to_string()), // eat the comma
                        (spans[2], "<E>, ".to_string()),
                        (spans[3], "F::X2".to_string()),
                        (spans[4], "G {}".to_string()),
                        (spans[5], "X {}".to_string()),
                        (spans[6], "Y {}".to_string()),
                        (spans[7], "Z {}".to_string()),
                    ],
                    Applicability::Unspecified,
                );

                err.emit();
            }

Some really awkward things in there, wondering if there's a better way:

  • Growing/shrinking spans seems to be really cumbersome, any tips on that being easier?
  • I have a span for the whole function definition, any tips for shrinking that to just cover the method name?
  • Given a span, how can I get the code snippet for that span? for things like the expression reordered
  • Given a type, how can I get a human readable string for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(My plan right now is to write a generic "lots of issues" path, and then special case for some nicer errors in simpler cases (ex: only one missing arg, etc.))

@estebank
Copy link
Contributor

estebank commented May 3, 2020

Also, it seems like you didn't run ./x.py test src/test/ui --bless, the tests are failing.

@Quantumplation Quantumplation marked this pull request as draft May 5, 2020 08:02
@JohnCSimon JohnCSimon added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels May 10, 2020
@Quantumplation
Copy link
Contributor Author

@JohnCSimon I mentioned above, I'm a bit stuck here. I'm not sure how to translate a Ty or an Expr into a human readable string for the error messages.

@Dylan-DPC-zz
Copy link

@estebank the author needs some guidance for this pr. Thanks

@Muirrum
Copy link
Member

Muirrum commented Aug 6, 2020

@Quantumplation What's the status of this PR?

@Quantumplation
Copy link
Contributor Author

@Muirrum All of the logic for determining WHAT to say in the error message is done, I just need to build the correct diagnostic. However, I ran into a few stumbling blocks, and when I didn't get any direction on those, it fell off my radar for a while.

Specifically, if I could get answers to the following questions (in relation to a code snippet I left in a comment above), I could finish this up fairly quickly:

  • Growing/shrinking spans seems to be really cumbersome, any tips on that being easier?
  • I have a span for the whole function definition, any tips for shrinking that to just cover the method name?
  • Given a span, how can I get the code snippet for that span? for things like the expression reordered
  • Given a type, how can I get a human readable string for it?

@Dylan-DPC-zz
Copy link

@estebank if you can answer the questions put forth in the last comment that unblock the author, we can push this forward :)

@jyn514 jyn514 added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Oct 22, 2020
This attempts to bring better error messages to invalid method calls, by applying some heuristics to identify common mistakes.

The algorithm is inspired by Levenshtein distance and longest common sub-sequence.   In essence, we treat the types of the function, and the types of the arguments you provided as two "words" and compute the edits to get from one to the other.

We then modify that algorithm to detect 4 cases:

 - A function input is missing
 - An extra argument was provided
 - The type of an argument is straight up invalid
 - Two arguments have been swapped
 - A subset of the arguments have been shuffled

(We detect the last two as separate cases so that we can detect two swaps, instead of 4 parameters permuted.)

It helps to understand this argument by paying special attention to terminology: "inputs" refers to the inputs being *expected* by the function, and "arguments" refers to what has been provided at the call site.

The basic sketch of the algorithm is as follows:

 - Construct a boolean grid, with a row for each argument, and a column for each input.  The cell [i, j] is true if the i'th argument could satisfy the j'th input.
 - If we find an argument that could satisfy no inputs, provided for an input that can't be satisfied by any other argument, we consider this an "invalid type".
 - Extra arguments are those that can't satisfy any input, provided for an input that *could* be satisfied by another argument.
 - Missing inputs are inputs that can't be satisfied by any argument, where the provided argument could satisfy another input
 - Swapped / Permuted arguments are identified with a cycle detection algorithm.

As each issue is found, we remove the relevant inputs / arguments and check for more issues.  If we find no issues, we match up any "valid" arguments, and start again.

Note that there's a lot of extra complexity:
 - We try to stay efficient on the happy path, only computing the diagonal until we find a problem, and then filling in the rest of the matrix.
 - Closure arguments are wrapped in a tuple and need to be unwrapped
 - We need to resolve closure types after the rest, to allow the most specific type constraints
 - We need to handle imported C functions that might be variadic in their inputs.

I tried to document a lot of this in comments in the code and keep the naming clear.
Much more to come as code review progresses.
In a lot of these cases, I prefer the way I formatted the code, but I'll defer to the tidy script :)
I still need to handle this properly, but I just threw this in to avoid the ICE.  I expect I'll have several rounds with code reviewers to refine things.
@Quantumplation
Copy link
Contributor Author

I just rebased this onto master. I'm still waiting for some answers to the above questions, I kind of forgot about this for a while. 😅 Over the next week I'll try to do some digging on my own, now that I understand Rust better and the rust-analyzer is working better on the code base to help me navigate, but if anyone has tips, it'd be much appreciated!

For some reason I missed that you could just format!(ty) and get a human
readable type for it.

There's still lots to work out
 - What heuristics should we have around when to display the complicated
   labels? ex: with just one error, it's probably better for the user to
   provide the error but not the suggestion?  Are "missing" parameter
   suggestions, where we don't have something to stick in there useful?
 - The semantics around the spans to use; when should we eat a comma,
   dealing with multiple inserted params, etc.
@rust-log-analyzer
Copy link
Collaborator

The job mingw-check failed! Check out the build log: (web) (plain)

Click to see the possible cause of the failure (guessed by this bot)
configure: rust.channel         := nightly
configure: rust.debug-assertions := True
configure: llvm.assertions      := True
configure: dist.missing-tools   := True
configure: build.configure-args := ['--enable-sccache', '--disable-manage-submodu ...
configure: writing `config.toml` in current directory
configure: 
configure: run `python /checkout/x.py --help`
configure: 
---
skip untracked path cpu-usage.csv during rustfmt invocations
skip untracked path src/doc/book/ during rustfmt invocations
skip untracked path src/doc/rust-by-example/ during rustfmt invocations
skip untracked path src/llvm-project/ during rustfmt invocations
Diff in /checkout/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs at line 627:
                         Issue::Invalid(arg) => {
                             let span = provided_args[arg].span;
                             let expected_type = expected_input_tys[arg];
-                            let found_type = final_arg_types.iter().filter(|(i, _, _)| *i == arg).next().unwrap().1;
Running `"/checkout/obj/build/x86_64-unknown-linux-gnu/stage0/bin/rustfmt" "--config-path" "/checkout" "--edition" "2018" "--unstable-features" "--skip-children" "--check" "/checkout/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs"` failed.
If you're running `tidy`, try again with `--bless`. Or, if you just want to format code, run `./x.py fmt` instead.
-                            labels.push((span, format!("expected {}, found {}", expected_type, found_type)));
+                            let found_type = final_arg_types
+                                .iter()
+                                .filter(|(i, _, _)| *i == arg)
+                                .next()
+                                .unwrap()
+                                .1;
+                            labels.push((
+                                span,
+                                format!("expected {}, found {}", expected_type, found_type),
+                            ));
                         }
                         Issue::Extra(arg) => {
                             // FIXME: This could probably be a lot cleaner, but I dunno how
Diff in /checkout/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs at line 635:
                             let span = provided_args[arg].span;
                             let hungry_span = if arg < provided_args.len() - 1 {
                                 // Eat the comma
-                                    span.lo(),
-                                    span.lo(),
-                                    span.hi() + BytePos(2),
-                                    span.ctxt(),
-                                )
+                                Span::new(span.lo(), span.hi() + BytePos(2), span.ctxt())
                                 span
                             };
                             };
Diff in /checkout/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs at line 650:
                                 .map(|ident| format!("fn {}(..)", ident))
                                 .unwrap_or("this function".to_string());
 
-                            labels.push((span, format!("no parameter of type {} is needed in {}", found_type, fn_name)));
+                            labels.push((
+                                span,
+                                format!(
+                                    "no parameter of type {} is needed in {}",
+                                    found_type, fn_name
+                            ));
+                            ));
                             if issue_count > 1 {
                                 suggestions.push((hungry_span, format!("")));
                             }
Diff in /checkout/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs at line 677:
                             };
                             };
                             let expected_type = expected_input_tys[arg];
-                            labels.push((missing_span, format!("missing argument of type {}", expected_type)));
+                            labels.push((
+                                missing_span,
+                                format!("missing argument of type {}", expected_type),
+                            ));
                             if issue_count > 1 {
-                                suggestions.push((missing_span, format!(" {{{}}},", expected_type)));
+                                suggestions
+                                    .push((missing_span, format!(" {{{}}},", expected_type)));
                         }
                         }
                         Issue::Swap(arg, other) => {
Diff in /checkout/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs at line 687:
                             let second_span = provided_args[other].span;
                             let first_snippet = source_map.span_to_snippet(first_span).unwrap();
                             let second_snippet = source_map.span_to_snippet(second_span).unwrap();
-                            let expected_types = (expected_input_tys[arg], expected_input_tys[other]);
+                            let expected_types =
+                                (expected_input_tys[arg], expected_input_tys[other]);
                             let found_types = (
-                                final_arg_types.iter().filter(|(i, _, _)| *i == arg).next().unwrap().1,
-                                final_arg_types.iter().filter(|(i, _, _)| *i == other).next().unwrap().1,
+                                final_arg_types
+                                    .iter()
+                                    .filter(|(i, _, _)| *i == arg)
+                                    .next()
+                                    .unwrap()
+                                    .1,
+                                final_arg_types
+                                    .iter()
+                                    .filter(|(i, _, _)| *i == other)
+                                    .next()
+                                    .unwrap()
+                                    .1,
                             );
-                            labels.push((first_span, format!("expected {}, found {}", expected_types.0, found_types.0)));
+                            labels.push((
+                                first_span,
+                                format!("expected {}, found {}", expected_types.0, found_types.0),
+                            ));
                             suggestions.push((first_span, second_snippet));
-                            labels.push((second_span, format!("expected {}, found {}", expected_types.1, found_types.1)));
+                            labels.push((
+                                second_span,
+                                format!("expected {}, found {}", expected_types.1, found_types.1),
+                            ));
                             suggestions.push((second_span, first_snippet));
                         }
                         Issue::Permutation(args) => {
Diff in /checkout/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs at line 704:
                                     let dst_span = provided_args[dst].span;
                                     let snippet = source_map.span_to_snippet(src_span).unwrap();
                                     let expected_type = expected_input_tys[dst];
-                                    let found_type = final_arg_types.iter().filter(|(i, _, _)| *i == dst).next().unwrap().1;
-                                    labels.push((dst_span, format!("expected {}, found {}", expected_type, found_type)));
+                                    let found_type = final_arg_types
+                                        .iter()
+                                        .filter(|(i, _, _)| *i == dst)
+                                        .next()
+                                        .unwrap()
+                                        .1;
+                                    labels.push((
+                                        dst_span,
+                                        format!("expected {}, found {}", expected_type, found_type),
+                                    ));
                                     suggestions.push((dst_span, snippet));
                             }
failed to run: /checkout/obj/build/bootstrap/debug/bootstrap test --stage 2 src/tools/tidy
Build completed unsuccessfully in 0:00:14

Copy link
Member

@jyn514 jyn514 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome ❤️

compiler/rustc_typeck/src/check/fn_ctxt/checks.rs Outdated Show resolved Hide resolved
compiler/rustc_typeck/src/check/fn_ctxt/checks.rs Outdated Show resolved Hide resolved
compiler/rustc_typeck/src/check/fn_ctxt/checks.rs Outdated Show resolved Hide resolved
Comment on lines 385 to 386
Swap(usize, usize),
Permutation(Vec<Option<usize>>),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are Swap and Permutation considered different?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC I did this so we could provide a better worded error message in the case of swapped arguments.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example:

fn my_func(A, B, C, D) {}

my_func(B, A, D, C); // Suggest two swaps, instead of a full permutation

compiler/rustc_typeck/src/check/fn_ctxt/checks.rs Outdated Show resolved Hide resolved
compiler/rustc_typeck/src/check/fn_ctxt/checks.rs Outdated Show resolved Hide resolved
compiler/rustc_typeck/src/check/fn_ctxt/checks.rs Outdated Show resolved Hide resolved
compiler/rustc_typeck/src/check/fn_ctxt/checks.rs Outdated Show resolved Hide resolved
compiler/rustc_typeck/src/check/fn_ctxt/checks.rs Outdated Show resolved Hide resolved
Co-authored-by: Joshua Nelson <joshua@yottadb.com>
@rust-log-analyzer
Copy link
Collaborator

The job mingw-check failed! Check out the build log: (web) (plain)

Click to see the possible cause of the failure (guessed by this bot)
configure: rust.channel         := nightly
configure: rust.debug-assertions := True
configure: llvm.assertions      := True
configure: dist.missing-tools   := True
configure: build.configure-args := ['--enable-sccache', '--disable-manage-submodu ...
configure: writing `config.toml` in current directory
configure: 
configure: run `python /checkout/x.py --help`
configure: 
---
skip untracked path cpu-usage.csv during rustfmt invocations
skip untracked path src/doc/book/ during rustfmt invocations
skip untracked path src/doc/rust-by-example/ during rustfmt invocations
skip untracked path src/llvm-project/ during rustfmt invocations
Diff in /checkout/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs at line 627:
                         Issue::Invalid(arg) => {
                             let span = provided_args[arg].span;
                             let expected_type = expected_input_tys[arg];
-                            let found_type = final_arg_types.iter().filter(|(i, _, _)| *i == arg).next().unwrap().1;
-                            labels.push((span, format!("expected {}, found {}", expected_type, found_type)));
+                            let found_type = final_arg_types
Running `"/checkout/obj/build/x86_64-unknown-linux-gnu/stage0/bin/rustfmt" "--config-path" "/checkout" "--edition" "2018" "--unstable-features" "--skip-children" "--check" "/checkout/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs"` failed.
If you're running `tidy`, try again with `--bless`. Or, if you just want to format code, run `./x.py fmt` instead.
+                                .iter()
+                                .filter(|(i, _, _)| *i == arg)
+                                .next()
+                                .unwrap()
+                                .1;
+                            labels.push((
+                                span,
+                                format!("expected {}, found {}", expected_type, found_type),
+                            ));
                         }
                         Issue::Extra(arg) => {
                             // FIXME: This could probably be a lot cleaner, but I dunno how
Diff in /checkout/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs at line 635:
                             let span = provided_args[arg].span;
                             let hungry_span = if arg < provided_args.len() - 1 {
                                 // Eat the comma
-                                    span.lo(),
-                                    span.lo(),
-                                    span.hi() + BytePos(2),
-                                    span.ctxt(),
-                                )
+                                Span::new(span.lo(), span.hi() + BytePos(2), span.ctxt())
                                 span
                             };
                             };
Diff in /checkout/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs at line 650:
                                 .map(|ident| format!("fn {}(..)", ident))
                                 .unwrap_or("this function".to_string());
 
-                            labels.push((span, format!("no parameter of type {} is needed in {}", found_type, fn_name)));
+                            labels.push((
+                                span,
+                                format!(
+                                    "no parameter of type {} is needed in {}",
+                                    found_type, fn_name
+                            ));
+                            ));
                             if issue_count > 1 {
                                 suggestions.push((hungry_span, format!("")));
                             }
Diff in /checkout/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs at line 677:
                             };
                             };
                             let expected_type = expected_input_tys[arg];
-                            labels.push((missing_span, format!("missing argument of type {}", expected_type)));
+                            labels.push((
+                                missing_span,
+                                format!("missing argument of type {}", expected_type),
+                            ));
                             if issue_count > 1 {
-                                suggestions.push((missing_span, format!(" {{{}}},", expected_type)));
+                                suggestions
+                                    .push((missing_span, format!(" {{{}}},", expected_type)));
                         }
                         }
                         Issue::Swap(arg, other) => {
Diff in /checkout/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs at line 687:
                             let second_span = provided_args[other].span;
                             let first_snippet = source_map.span_to_snippet(first_span).unwrap();
                             let second_snippet = source_map.span_to_snippet(second_span).unwrap();
-                            let expected_types = (expected_input_tys[arg], expected_input_tys[other]);
+                            let expected_types =
+                                (expected_input_tys[arg], expected_input_tys[other]);
                             let found_types = (
-                                final_arg_types.iter().filter(|(i, _, _)| *i == arg).next().unwrap().1,
-                                final_arg_types.iter().filter(|(i, _, _)| *i == other).next().unwrap().1,
+                                final_arg_types
+                                    .iter()
+                                    .filter(|(i, _, _)| *i == arg)
+                                    .next()
+                                    .unwrap()
+                                    .1,
+                                final_arg_types
+                                    .iter()
+                                    .filter(|(i, _, _)| *i == other)
+                                    .next()
+                                    .unwrap()
+                                    .1,
                             );
-                            labels.push((first_span, format!("expected {}, found {}", expected_types.0, found_types.0)));
+                            labels.push((
+                                first_span,
+                                format!("expected {}, found {}", expected_types.0, found_types.0),
+                            ));
                             suggestions.push((first_span, second_snippet));
-                            labels.push((second_span, format!("expected {}, found {}", expected_types.1, found_types.1)));
+                            labels.push((
+                                second_span,
+                                format!("expected {}, found {}", expected_types.1, found_types.1),
+                            ));
                             suggestions.push((second_span, first_snippet));
                         }
                         Issue::Permutation(args) => {
Diff in /checkout/compiler/rustc_typeck/src/check/fn_ctxt/checks.rs at line 704:
                                     let dst_span = provided_args[dst].span;
                                     let snippet = source_map.span_to_snippet(src_span).unwrap();
                                     let expected_type = expected_input_tys[dst];
-                                    let found_type = final_arg_types.iter().filter(|(i, _, _)| *i == dst).next().unwrap().1;
-                                    labels.push((dst_span, format!("expected {}, found {}", expected_type, found_type)));
+                                    let found_type = final_arg_types
+                                        .iter()
+                                        .filter(|(i, _, _)| *i == dst)
+                                        .next()
+                                        .unwrap()
+                                        .1;
+                                    labels.push((
+                                        dst_span,
+                                        format!("expected {}, found {}", expected_type, found_type),
+                                    ));
                                     suggestions.push((dst_span, snippet));
                             }
failed to run: /checkout/obj/build/bootstrap/debug/bootstrap test --stage 2 src/tools/tidy
Build completed unsuccessfully in 0:00:19

There were some test cases where "" wasn't typechecking against &str in
the "soft" typechecking path (the one that doesn't persist new
constraints.)

I found a method that appears to typecheck without persisting new
constraints, but I can't be sure, so I'll need to double check that.
For now, this new expression typechecking method works well.

This also tries to carefully handle eliminating extra arguments.  Spans
are tricky, as we need to eliminate commas correctly, and the suggestion
code gets angry if we have overlapping spans, so this tries to find
contiguous blocks to eliminate.

I'll probably simplify this now that I understand the logic a bit
better, but it's late, so I'm going to bed.
src/test/ui/argument-suggestions/basic.stderr Outdated Show resolved Hide resolved
src/test/ui/argument-suggestions/complex.stderr Outdated Show resolved Hide resolved
src/test/ui/argument-suggestions/extra_arguments.stderr Outdated Show resolved Hide resolved
| | | |
| | | this parameter of type {float} isn't needed for one_arg
| | this parameter of type &'static str isn't needed for one_arg
| help: removing these arguments may help
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| help: removing these arguments may help
| help: remove these arguments

It would be really nice to give a structured suggestion: one_arg(1);, but I understand if that's tricky.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as far as I can tell, if you only provide one suggestion, it renders it inline like this, but if you provide more than one, it renders as a separate block. So this is showing up this way because of my refactor to handle commas correctly I'm deleting multiple arguments in one block, rather than each individual block. I might split it up again, the logic for handling the commas correctly is just tricky lol.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider using span_suggestion_verbose to make it always render on its own subwindow

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no multipart_suggestion_verbose; should I add it? Should I record each change as a separate suggestion?

Copy link
Contributor

@estebank estebank Jan 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that multipart_suggestions are always verbose (there is no way to display them without them being shown in a span label unless there's a single suggestion... at which point I think the inline output would be desired).

Edit: adding it is reasonable.

Pi Lanningham and others added 7 commits January 16, 2021 02:06
This forces it to always show the subwindow for the suggestion.  Also uses HasPlaceholders when appropriate.
The 'extra' spans are one iteration better, but still wrong, especially when there are arguments missing in the middle; Need another pass on this
Rather than dropping individual suggestion spans, I just re-construct
the function call from scratch.  This makes fiddling with commas sooo
much easier.

The highlighting of individual errors is temporarily disabled, I'll add
that back in temporarily.
@estebank suggested we only annotate these if there are 5 or less.

In addition to the suggestion at the bottom of the error, we label the
specific problems we encounter without suggesting how to fix it.

This also ladders the suggestion text: if there's just one thing to
suggest, use specific language about the suggestion; however, if there
are multiple suggestions, use a generic "did you mean"

Even though the comment on span_suggestion suggests against this,
Camelid on zulip pointed out that there are already a few suggestions
like this, and any other language sounds slightly awkward.
I left several tests failing, as they are likely cases my code needs to handle
@Dylan-DPC-zz
Copy link

@Quantumplation any updates?

@Quantumplation
Copy link
Contributor Author

@Dylan-DPC Yea; a few weeks ago I prodded people on zulip again and got some answers that unblocked me, so I've been working through this again. The current status is: it works for the obvious cases, and there's a ton of corner cases in tests to handle now, that I'm trying to work through.

The things I'm currently trying to figure out:

  • Pretty confused as to what error codes I should use; should I use a new one, since the error message is different? should I try to map the cases onto existing error codes? etc.

  • Lots of "descriptive" corner cases my code falls down on; sometimes it comes up with a type _, when the previous code suggested a good type, or it's missing descriptive keywords like struct, etc.

  • Some weird corner cases around inferred associated types, interestingly the suggestions are good, but the error message highlighting it is odd

  • Lots of regressions on corner case suggestions for some reason, like "consider borrowing", "consider deref", etc. I didn't see that in the original code, so I'm not sure how I deflected things downstream such that those suggestions aren't showing up.

@Dylan-DPC-zz
Copy link

@estebank can you answer these questions and help the author? thanks

Copy link
Member

@jackh726 jackh726 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this PR will be easier to review if you can split a few trivial things into a separate PR.

@@ -307,6 +307,28 @@ impl Diagnostic {
self
}

/// Show a suggestion that has multiple parts to it, always as it's own subwindow.
/// In other words, multiple changes need to be applied as part of this suggestion.
pub fn multipart_suggestion_verbose(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you split this into a separate PR?

@@ -280,6 +280,20 @@ impl<'a> DiagnosticBuilder<'a> {
self
}

/// See [`Diagnostic::multipart_suggestion_verbose()`].
pub fn multipart_suggestion_verbose(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This too?

Comment on lines +97 to +107
// Span enclosing the call site
call_span: Span,
// Expression of the call site
call_expr: &'tcx hir::Expr<'tcx>,
// Types (as defined in the *signature* of the target function)
formal_input_tys: &[Ty<'tcx>],
// More specific expected types, after unifying with caller output types
expected_input_tys: &[Ty<'tcx>],
// The expressions for each provided argument
provided_args: &'tcx [hir::Expr<'tcx>],
// Whether the function is variadic, for example when imported from C
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the names/comments here. Can you split them into a separate PR too?

@jackh726
Copy link
Member

@Quantumplation are you still around? If so, I'd like to help move this PR forward.

To start, there are a few changes that I think would be nice to move to a separate PR and would make this review easier.

r? @jackh726

@rust-highfive rust-highfive assigned jackh726 and unassigned estebank Oct 21, 2021
@Quantumplation
Copy link
Contributor Author

@jackh726 I would love to! I've been super busy with my startup recently, hence why it's fallen by the wayside, but I'm happy to help get this over the finish line!

@jackh726 jackh726 added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 1, 2021
@jackh726
Copy link
Member

Btw I've rebased this branch and have fixed some of the error messages (including re-emitting some of the deleted suggestions) here: https://github.com/jackh726/rust/tree/Quantumplation/65853%2Fparam-heuristics

I'm planning on starting to submit PRs incrementally (to whatever extent possible) for this.

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Jan 5, 2022
…dtwco

Some cleanups around check_argument_types

Split out in ways from my rebase/continuation of rust-lang#71827

Commits are mostly self-explanatory and these changes should be fairly straightforward
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Jan 5, 2022
…dtwco

Some cleanups around check_argument_types

Split out in ways from my rebase/continuation of rust-lang#71827

Commits are mostly self-explanatory and these changes should be fairly straightforward
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Jan 6, 2022
…dtwco

Some cleanups around check_argument_types

Split out in ways from my rebase/continuation of rust-lang#71827

Commits are mostly self-explanatory and these changes should be fairly straightforward
@jackh726
Copy link
Member

jackh726 commented Mar 1, 2022

Closing in favor of #92364

@jackh726 jackh726 closed this Mar 1, 2022
bors added a commit to rust-lang-ci/rust that referenced this pull request Apr 16, 2022
…euristics, r=estebank

Better method call error messages

Rebase/continuation of rust-lang#71827

~Based on rust-lang#92360~
~Based on rust-lang#93118~

There's a decent description in rust-lang#71827 that I won't copy here (for now at least)

In addition to rebasing, I've tried to restore most of the original suggestions for invalid arguments. Unfortunately, this does make some of the errors a bit verbose. To fix this will require a bit of refactoring to some of the generalized error suggestion functions, and I just don't have the time to go into it right now.

I think this is in a state that the error messages are overall better than before without a reduction in the suggestions given.

~I've tried to split out some of the easier and self-contained changes into separate commits (mostly in rust-lang#92360, but also one here). There might be more than can be done here, but again just lacking time.~

r? `@estebank` as the original reviewer of rust-lang#71827
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Suggestion: provide better report of function argument mismatch