10000 Elements names are shadowed on autocomplete and usability · Issue #56821 · dart-lang/sdk · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Elements names are shadowed on autocomplete and usability #56821

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

Open
FMorschel opened this issue Sep 30, 2024 · 14 comments
Open

Elements names are shadowed on autocomplete and usability #56821

FMorschel opened this issue Sep 30, 2024 · 14 comments
Labels
area-devexp For issues related to the analysis server, IDE support, linter, `dart fix`, and diagnostic messages. devexp-completion Issues with the analysis server's code completion feature P3 A lower priority bug or feature request type-enhancement A request for a change that isn't a bug

Comments

@FMorschel
Copy link
Contributor
FMorschel commented Sep 30, 2024

Partially inspired by Dart-Code/Dart-Code#5242.

If you have the following code:

class C {
  void foo(int value) {
    bar                   // <<-- Referencing line
  }

  static void bar() {}
}

extension EC on C {
  int get bar => 0;
}

int bar() => 0;

There is no current indication that either the top declaration bar exists or that we could do EC(this).bar to use the declaration of bar for the extension.

This is all working as intended, of course, but I'd like to see a differentiation on the autocomplete options so the user can easily see that there is name shadowing here.

This is even more crucial in cases like:

a.dart

class C {}
class MyClass {}

thing.dart

class A {
  late int bar;
}

other.dart

import 'a.dart' as bar;
import 'thing.dart';

class C extends A {
  void foo(int value) {
    this.bar.isEven;
    bar
  }
}

This is the current output of the autocomplete:

image

If I add the .

image

In this case, we could easily surpass this by adding the this. at the start similar to what we do with aliased imports and its elements, but we have no indication of it currently.

As a reference, I stumbled upon this issue in the last case, so it took me a while to figure out that my variable was being shadowed by the import.


I'm not sure what we could do for:

import 'a.dart' as bar;

class C {
  late int bar;
}

Since both are valid things but in this case if I was trying to use the import inside C I would never be able to unless I renamed it (which we would probably want) but then it is not easy to see that this is what the user is supposed to do.


@DanTup

@dart-github-bot
Copy link
Collaborator

Summary: The issue reports that Dart's autocomplete doesn't clearly indicate name shadowing, making it difficult for users to identify and resolve conflicts between variables, imports, and extensions. The user suggests improving autocomplete by differentiating shadowed elements, potentially by adding a visual cue or a "this." prefix.

@dart-github-bot dart-github-bot added legacy-area-analyzer Use area-devexp instead. triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. type-enhancement A request for a change that isn't a bug labels Sep 30, 2024
@bwilkerson
Copy link
Member

There is no current indication that either the top declaration bar exists ...

There's no completion suggestion because there's no way to access it. That is, there's no valid code you can write at that location to get around the fact that the local declaration hides the top-level declaration.

... or that we could do EC(this).bar to use the declaration of bar for the extension.

Yes, we could add a case for that. I would only want to suggest that form when the extension method can't be reached because it's hidden. It's not a form of code that we want to promote.

I'm not sure code completion is written in such as way as to make it performant to add this support. Something that would require some investigation.

In this case, we could easily surpass this by adding the this. at the start ...

I'd be ok with that, but again, only in cases where the this. is necessary to access something that would otherwise be hidden.

@FMorschel
Copy link
Contributor Author

There's no completion suggestion because there's no way to access it. That is, there's no valid code you can write at that location to get around the fact that the local declaration hides the top-level declaration.

Yes, I know.

The point I was trying to bring here was that we could maybe think of a way for us to tell the user things are being hidden. I'm not sure how, but that is the gist of this issue.

Also, if someone can think of a better name for this please feel free to update it, I could not think of a name that would satisfy me.

@DanTup
Copy link
Collaborator
DanTup commented Sep 30, 2024

I like the idea of including this.bar in the case where bar would be something different (possibly super. would also work). Same for E(this) for extension members too.

I'm less sure there's a simple solution to accessing the local function though. I think there is a way it could work, but it's weird and I don't know if it would be used enough to be worthwhile making it work.

You can use import ''; to import the current file. That that means you can use import '' as me; to import the current library as me, which would then allow accessing me.bar(). So in theory, we could include me.bar() in the completion, and have an auto-import that adds it (at least, if we ignore for a minute that we don't currently support prefixes on completion auto-imports).

I'm not sure if it would be very obvious to users if they saw me.bar (or whatever we called me), and if they did accept it, I'm not sure they would understand why they have an import '' because I doubt using it is very common (probably it could also be its own filename too, though I'm not certain if they're always equivalent). Maybe it'd be slightly nicer if Dart had an identifier that resolved to the current library without needing that extra import... that would avoid the need for any additional import. I think this might be similar to C#'s global::.

@bwilkerson
Copy link
Member

If the problem is shadowing, then we could potentially introduce a lint to flag all shadowing (we do have lints for some specific kinds of shadowing).

Or we could add a highlight modifier to allow users to color declarations that shadow another declaration.

But I don't think that code completion is the right way to solve this particular problem.

@DanTup
Copy link
Collaborator
DanTup commented Sep 30, 2024

I like both of those ideas - a lint would allow you to forbid having conflicting names if you wanted, or you could use colouring if you just wanted them to be more visible in code :-)

@FMorschel
Copy link
Contributor Author
FMorschel commented Sep 30, 2024

But I don't think that code completion is the right way to solve this particular problem.

It could solve part of this problem, as we discussed, but probably you're right that it will not solve all of it. Maybe a lint, as you said, when the user writes bar for the above code on the scope that has shadowing to let them know there are other possibilities? Because of things like the second example (import and variable inside a superclass) there is no way of knowing about that I don't think.


FYI:

You can use import ''; to import the current file. That that means you can use import '' as me; to import the current library as me, which would then allow accessing me.bar(). So in theory, we could include me.bar() in the completion, and have an auto-import that adds it (at least, if we ignore for a minute that we don't currently support prefixes on completion auto-imports).

There is a discussion on using this kind of workarounds here: Should "self" importing trigger unnecessary_import

@scheglov scheglov added the P3 A lower priority bug or feature request label Sep 30, 2024
@lrhn lrhn removed the triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. label Oct 1, 2024
@bwilkerson bwilkerson added devexp-completion Issues with the analysis server's code completion feature area-devexp For issues related to the analysis server, IDE support, linter, `dart fix`, and diagnostic messages. and removed legacy-area-analyzer Use area-devexp instead. labels Feb 28, 2025
@FMorschel
Copy link
Contributor Author

If the problem is shadowing, then we could potentially introduce a lint to flag all shadowing (we do have lints for some specific kinds of shadowing).

I found only avoid_shadowing_type_parameters. I'm not sure what other lints you had in mind.

@FMorschel
Copy link
Contributor Author

I was thinking about Dart-Code/Dart-Code#5242 where we added the two suggestions (both prefixes) on completion:

import 'my_class_name.dart' as i;
import 'second.dart' as s;


void main() {
  f2(My^);
}

void f1(i.MyClassName c) {}
void f2(s.MyClassName c) {}

Something I'd like to suggest for the above case, could we also add this.bar at the completion here?

import 'a.dart' as bar;
import 'thing.dart';

class C extends A {
  void foo(int value) {
    this.bar.isEven;
    bar^
  }
}

I'd vote for it to also happen when shadowed by local variables, too. I'm not sure it would be easy, but it would tell the user this is something that exists and you can access it.

Of course, it would go away if the user writes . or something like that and doesn't use the completion, but I think it would be a good thing here.

@bwilkerson
Copy link
Member

I'm not sure what other lints you had in mind.

By "potentially introduce" I meant that we could implement a new lint to cover this case.

Something I'd like to suggest for the above case, could we also add this.bar at the completion here?

I think that would be reasonable. And yes, it should be offered no matter what kind of declaration is shadowing the member.

@FMorschel
Copy link
Contributor Author
FMorschel commented Apr 17, 2025

I'm not sure if this was ever considered, but what about cases like this:

abstract class A {
  int value = 0;

  void m() {
    value^;
  }
}

extension FirstExtension on A {
  int value() => 0;
}

extension SecondExtension on A {
  int get value => 0;
}

void f(A Function() a) {
  a().value;
}

I'm sure it is pretty unusual, and if we did that, we might want to handle inside f too. Could we maybe add something like FistExtension(...).value to the completion list? Of course, only when the extension is named. Not too invested here, but it would be a nice way of letting people know this is possible.

This would be especially great because if we remove int value = 0; from inside A, we get an ambiguous_extension_member_access and a completion that resolves that would be my ideal flow (I opened #60561 about the quick-fix).


Edit: I'm not even sure this is possible for auto-complete, just a suggestion. I think @DanTup once talked to me about not being able to edit anything written before it, so I don't think it is.

@FMorschel
Copy link
Contributor Author

I'm not even sure this is possible for auto-complete, just a suggestion. I think DanTup once talked to me about not being able to edit anything written before it, so I don't think it is.

After a bit more thinking, I think for the example I gave above, it is possible. When nothing is written before, not even this. Not sure what the team thinks about this but I'd like to have that option.

@bwilkerson
Copy link
Member

... we get an ambiguous_extension_member_access and a completion that resolves that ...

We already have fixes to resolve this issue, I don't think that using code completion to correct errors is a good path forward.

As for all the rest of the suggestions, (a) we have to consider how common these cases are and decide whether it's worth the opportunity cost of not doing something else that might help more users, and (b) the more 'uncommon' cases we add to code completion the more likely it is that one or more of them will degrade the performance for the more common cases.

While I completely agree that part of the value of tooling is to help users learn parts of the language and framework that they're not familiar with, sometimes the cost of doing so is too high, and I think this might be one such time.

@DanTup
Copy link
Collaborator
DanTup commented Apr 22, 2025

Edit: I'm not even sure this is possible for auto-complete, just a suggestion. I think @DanTup once talked to me about not being able to edit anything written before it, so I don't think it is.

I don't recall the specifics of that conversation, but:

When we produce a completion for LSP, we can attach to it a "replacement range", which is the range that will be replaced when accepting the completion, if completion mode is "replace" (which is out default).

So for:

aaa(bbb^());

The replacement range here would be "bbb". We can replace "bbb" with whatever we want (for example "this.bbb") but we can't insert text before aaa for example (with one exception - detailed below). Things get a bit weird if you use insert mode, but that's a bit weird generally (eg. you type foo then complete fooBar and get foofooBar) which is why we default to replacement.

There is an exception... We can provide additional text edits with a completion that are applied as a best effort by VS Code and may silently be discarded (because VS Code might not be able to rewrite those edits to account for changes made to the document since completion was computed). We use these for adding import statements. I'm not sure these could be adjacent to the main edit though (there are a bunch of limitations and caveats to this but I don't know if they're well detailed, or just spread across a bunch of VS Code issues).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-devexp For issues related to the analysis server, IDE support, linter, `dart fix`, and diagnostic messages. devexp-completion Issues with the analysis server's code completion feature P3 A lower priority bug or feature request type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

6 participants
0