Skip to content
This repository was archived by the owner on Nov 24, 2023. It is now read-only.
This repository was archived by the owner on Nov 24, 2023. It is now read-only.

Handle Insert-only suggestions #103

Closed
@killercup

Description

@killercup

What?

There are lints that give suggestions that only insert new snippets, but don't replace anything. An example I've found in rust-lang/rust#50713 was the missing-comma-in-match lint.

Examples for "insert only"

Let's look at this example code:

fn main() {
    match &Some(3) {
        &None => 1
        &Some(2) => { 3 }
        _ => 2
    };
}

Rustc warns us that

error: expected one of `,`, `.`, `?`, `}`, or an operator, found `=>`
 --> src/main.rs:4:18
  |
3 |         &None => 1
  |                   - help: missing a comma here to end this `match` arm
4 |         &Some(2) => { 3 }
  |                  ^^ expected one of `,`, `.`, `?`, `}`, or an operator here

The interesting part in the JSON diagnostics output is this:

{
  "message": "expected one of `,`, `.`, `?`, `}`, or an operator, found `=>`",
  "children": [{
    "message": "missing a comma here to end this `match` arm",
    "spans": [{
      "byte_start": 51,
      "byte_end": 51,
      "suggested_replacement": ","
    }]
  }]
}

Note how the start and end byte index are the same here, which is the \n in this case.

Full JSON output
{
  "message": "expected one of `,`, `.`, `?`, `}`, or an operator, found `=>`",
  "code": null,
  "level": "error",
  "spans": [
    {
      "file_name": "./tests/everything/missing-comma.rs",
      "byte_start": 69,
      "byte_end": 71,
      "line_start": 4,
      "line_end": 4,
      "column_start": 18,
      "column_end": 20,
      "is_primary": true,
      "text": [
        {
          "text": "        &Some(2) => { 3 }",
          "highlight_start": 18,
          "highlight_end": 20
        }
      ],
      "label": "expected one of `,`, `.`, `?`, `}`, or an operator here",
      "suggested_replacement": null,
      "expansion": null
    }
  ],
  "children": [
    {
      "message": "missing a comma here to end this `match` arm",
      "code": null,
      "level": "help",
      "spans": [
        {
          "file_name": "./tests/everything/missing-comma.rs",
          "byte_start": 51,
          "byte_end": 51,
          "line_start": 3,
          "line_end": 3,
          "column_start": 19,
          "column_end": 19,
          "is_primary": true,
          "text": [
            {
              "text": "        &None => 1",
              "highlight_start": 19,
              "highlight_end": 19
            }
          ],
          "label": null,
          "suggested_replacement": ",",
          "suggestion_applicability": "Unspecified",
          "expansion": null
        }
      ],
      "children": [],
      "rendered": null
    }
  ],
  "rendered": "error: expected one of `,`, `.`, `?`, `}`, or an operator, found `=>`\n --> ./tests/everything/missing-comma.rs:4:18\n  |\n3 |         &None => 1\n  |                   - help: missing a comma here to end this `match` arm\n4 |         &Some(2) => { 3 }\n  |                  ^^ expected one of `,`, `.`, `?`, `}`, or an operator here\n\n"
}
{
  "message": "aborting due to previous error",
  "code": null,
  "level": "error",
  "spans": [],
  "children": [],
  "rendered": "error: aborting due to previous error\n\n"
}

Detect "insert only"

Now, I was wondering: Is the byte_start == byte_end a good enough indicator for "insert only"? Or is it also used for "replace the character at position X"? I've come up with the following example that triggers a "replaces only one char" suggestion:

fn main() {
    let x = 42;
}

rendered:

warning: unused variable: `x`
 --> src/main.rs:2:9
  |
2 |     let x = 42;
  |         ^ help: consider using `_x` instead
  |
  = note: #[warn(unused_variables)] on by default

or, in (reduced) JSON:

{
  "message": "unused variable: `x`",
  "children": [{
      "message": "consider using `_x` instead",
      "spans": [{
        "byte_start": 20,
        "byte_end": 21,
        "suggested_replacement": "_x",
      }]
  }]
}

So, usually the byte range seems to span at least one character. Which is great -- because rustfix uses inclusive ranges internally and it totally depends on this behavior right now:

https://github.com/rust-lang-nursery/rustfix/blob/ebca9b67a1af4acd3196d22a2fcfc51f1edea778/src/lib.rs#L199-L202

Indeed, the original suggestion even makes rustfix bail out of fixing it:

https://github.com/rust-lang-nursery/rustfix/blob/ebca9b67a1af4acd3196d22a2fcfc51f1edea778/src/replace.rs#L64-L69

How to fix this

  • Replace the ensure! with a let insert_only = from > up_to_and_including;
  • Special-case based on insert_only where we split a chunk into two where the inserted new chunk has a "useful" index
  • Ensure this works by adding tests that trigger this
  • Decide if we want to bail out when appending twice after the same index

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions