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

Quick-fixes that insert text with completion/choices show backslashes in front of closing curly braces #54403

Closed
FMorschel opened this issue Dec 15, 2023 · 9 comments
Assignees
Labels
analyzer-lsp Issues with analysis server's support of Language Server Protocol analyzer-quick-fix area-analyzer Use area-analyzer for Dart analyzer issues, including the analysis server and code completion. P2 A bug or feature request we're likely to work on type-bug Incorrect behavior (everything from a crash to more subtle misbehavior)

Comments

@FMorschel
Copy link
Contributor

Describe the issue

When using the quick-fix for missing override, with an Iterable<Record> (tested with Set and List as well), the last item inside the record always ends with \ at the end.

To Reproduce
Steps to reproduce the behavior:

  1. Add the following code to your project in vscode:
abstract class A {
  void fn(Iterable<({int a, int b})> r);
}

class B extends A {
}

abstract class C {
  void fn(({int a, int b}) r);
}

class D extends C {
}
  1. Quick fix for missing override.

Expected behavior

Expected the quick fix to not add \ at the end.

Screenshots

20231215_104425.mp4
@FMorschel
Copy link
Contributor Author

FMorschel commented Dec 15, 2023

Also, maybe this should be a separate issue but I'll ask here anyway just to be sure.

If you do:

abstract class C {
  void fn(({int b, int a}) r);
}

class D extends C {
}

Using the quick fix reorders the named values to be in alphabetical order:

abstract class C {
  void fn(({int b, int a}) r);
}

class D extends C {
  @override
  void fn(({int a, int b}) r) {
    // TODO: implement fn
  }
}

That could make some users a little confused when searching by text for the same record that was originally written (which happened to me). Is this intended and if so, why?

@bwilkerson
Copy link
Member

Thanks for reporting the problem with the slash!

As for the re-ordering, that's an unfortunate consequence of the internal implementation and/or specification.

Two records types are considered to be the same if the number and types of the positional fields match, and if the names and types of the named fields match. In other words, the order of the named fields isn't considered when comparing record types.

In order to implement this efficiently, the analyzer normalizes the named fields in it's internal representation. Unfortunately, that means that we've lost the order in which the fields were declared by the time we get to the fix, so there's no way we can duplicate the original order.

@FMorschel
Copy link
Contributor Author

FMorschel commented Dec 15, 2023

I see, thanks for clarifying!

Just a new update. tested with a Map as well, the problem seems to be when the Record is inside a <> pair and it only happens when there is a named value inside the Record

@FMorschel FMorschel changed the title non_abstract_class_inherits_abstract_member quick fix bug with Iterable<Record> non_abstract_class_inherits_abstract_member quick fix bug with <Record> Dec 15, 2023
@FMorschel FMorschel changed the title non_abstract_class_inherits_abstract_member quick fix bug with <Record> non_abstract_class_inherits_abstract_member quick fix bug with last named value in Record inside <> pair Dec 15, 2023
@bwilkerson
Copy link
Member

I'm not sure where the problem might be. I was able to reproduce the issue in VS Code using a build from HEAD, with

abstract class A {
  void m(Iterable<({int a, int b})> r);
}

class B extends A {}

I thought I had an idea of where the bug might be, so I wrote a test of DartEditBuilder.writeType, but that passed. So then I wrote a test of DartEditBuilder.writeOverride and that test passed too. Then I wrote a test of CreateMissingOverrides and that passed. So I added a protocol-level test and that passed (as in, we're passing the right result back to VS Code).

@DanTup It's possible that my test of LSP isn't right, but it looks like the right edit is getting returned. Is there any chance the bug is on the DartCode or VS Code side?

@FMorschel
Copy link
Contributor Author

Tested on IntelliJ CE, no issues. Seems to me that the problem is only with VSCode

@DanTup
Copy link
Collaborator

DanTup commented Dec 15, 2023

Here's what the edit looks like in the JSON from the server:

{
	"insertTextFormat": 2,
	"newText": "$0\n  @override\n  void m(${1|Iterable<({int a\\, int b\\})>,Object|} ${2:r}) {\n    // TODO: implement m\n  }\n",
	"range": {
		"end": {
			"character": 19,
			"line": 4
		},
		"start": {
			"character": 19,
			"line": 4
		}
	}
}

This looks correct to me - there are two backslashes - one to escape the other backslash in the string, meaning the actual content looks like:

$0
  @override
  void m(${1|Iterable<({int a\, int b\})>,Object|} ${2:r}) {
    // TODO: implement m
  }

The remaining backslashes are to escape the comma (so it's not a separator between the two choices) and the closing bracket (so it doesn't end the choice block) - this seems to match what's documented here.

So assuming I'm interpreting the escaping rules correctly, I think it's either a bug in Dart-Code (if we're doing any massaging of this value), or VS Code. Will take a look.

@DanTup
Copy link
Collaborator

DanTup commented Dec 16, 2023

IMO this is a VS Code bug, because we're escaping a character that the docs say can be escaped. However, it could be argued that the escaping is unnecessary in a choice, because they're surrounded by {| and |}. I've opened microsoft/vscode#201059 for confirmation - if it's working as intended then hopefully the docs can be made more specific, and we'll have to change the escaping in the server to match. That change would likely be here:

static final _escapeSnippetChoiceTextRegex =
RegExp(r'[$}\\\|,]'); // Replace any of $ } \ | ,

@srawlins srawlins transferred this issue from dart-lang/linter Dec 18, 2023
@srawlins srawlins added area-analyzer Use area-analyzer for Dart analyzer issues, including the analysis server and code completion. analyzer-quick-fix P2 A bug or feature request we're likely to work on type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) labels Dec 18, 2023
@DanTup DanTup added the analyzer-lsp Issues with analysis server's support of Language Server Protocol label Dec 18, 2023
@DanTup DanTup self-assigned this Dec 18, 2023
@DanTup
Copy link
Collaborator

DanTup commented Dec 19, 2023

Apparently this isn't a bug and we should not escape any characters for the specific cases that don't require it (eg. in choices, we cannot escape } even though we escape it on the others).

I've opened some PRs to VS Code + LSP docs to try and make this concrete before I start changing things:

microsoft/language-server-protocol#1868
microsoft/vscode-docs#6928

@DanTup DanTup changed the title non_abstract_class_inherits_abstract_member quick fix bug with last named value in Record inside <> pair Quick-fixes that insert text with completion/choices show backslashes in front of closing curly braces Dec 19, 2023
@DanTup
Copy link
Collaborator

DanTup commented Jan 11, 2024

The VS Code docs + LSP changes landed. I've opened a server fix at https://dart-review.googlesource.com/c/sdk/+/345661

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
analyzer-lsp Issues with analysis server's support of Language Server Protocol analyzer-quick-fix area-analyzer Use area-analyzer for Dart analyzer issues, including the analysis server and code completion. P2 A bug or feature request we're likely to work on type-bug Incorrect behavior (everything from a crash to more subtle misbehavior)
Projects
None yet
Development

No branches or pull requests

4 participants