microsoft / pyright

Static Type Checker for Python
Other
13.25k stars 1.43k forks source link

reportUnusedVariable ignore pattern #1118

Closed smackesey closed 2 years ago

smackesey commented 3 years ago

Is your feature request related to a problem? Please describe.

PyRight at present seems to have no way to fine tune the reportUnusedVariable setting to ignore variables matching certain patterns. When implementing a function/method with a third-party-specified interface, it often happens that there will be some function arguments that you write out but don't use. It should be possible to suppress diagnostics for these "expected" unused arguments without turning off unused variable warnings entirely. But at present, it appears that reportUnusedVariable can only be toggled entirely on or off.

Describe the solution you'd like

A common convention for this situation is to prefix the unused arguments with _. Other static analysis tools permit specification of a pattern that matches variable names for which to suppress unused warnings (see varsIgnorePattern for eslint's no-unused-vars) A similar option could be implemented in PyRight.

erictraut commented 3 years ago

Can you provide an example of a "function argument that you write out but don't use"? Function arguments are expressions, and they represent "a use" of a variable, so I'm a bit confused by what you mean here.

Pyright suppresses this diagnostic rule if the variable is named _. If you want to disable the rule completely, you can do so on a file-by-file basis (using "# pyright: reportUnusedVariable=false") or for the entire workspace or user (using the settings).

smackesey commented 3 years ago

I don't mean a function argument in the call, I mean in the signature (so, not an expression). A simple example is a method that doesn't use self:

class A:
    def my_method(self):  # this will be flagged with an `unusedVariable` because `self` is not used
        print('this is my method')

It is good to know that PyRight suppresses the warning if the variable is named _, but oftentimes one wants to keep the variable name for readability purposes (and in case you change the method to actually use the variable). Also one might leave multiple arguments (which can't have the same name) unused. That is why one cross-language convention is to mark unused variables/arguments with a leading underscore, like _variable.

erictraut commented 3 years ago

Function parameters (even if they are unused) never trigger the reportUnusedVariable diagnostic rule. They will be displayed as "grayed out" if they are unused and your editor supports that feature, but no error or warning is emitted in this case.

smackesey commented 3 years ago

Hmm, I am getting a warning from Pylance, which I understand uses Pyright and works off the Pyright config file. But maybe Pylance's warning in this case is being generated by different machinery.

erictraut commented 3 years ago

You are seeing this with the code snippet you provided above? Or with some other code? Can you provide either a screen shot or a code snippet?

smackesey commented 3 years ago

OK, I'm sorry, it is an information rather than warning diagnostic. My editor (NeoVim/CoC) displays "information" diagnostics similarly to how it displays "error" and "warning" diagnostics. But I suppose it is the client's responsibility to display this appropriately, rather than PyRight's responsibility not to send the information. Thanks for your help, I'll close the issue.

erictraut commented 3 years ago

OK, I'm confused on two points:

  1. You said you were running Pylance, but Pylance works only with VS Code. Are you sure you're not using Pyright? It works with NeoVim. Or perhaps you just used Pylance with VS Code to try to repro this problem?

  2. Pyright does not emit these as "information" diagnostics. It emits them as "hint" diagnostics with a special "tag" that indicates that it should be displayed as "grayed out". Pyright explicitly checks to see if the client supports this tag and does not emit these hint diagnostics if the client says that it doesn't support the tag. I specifically added this check for clients like NeoVim that don't support the "grayed out text" functionality. Based on what you're seeing, it appears that NeoVim is reporting that it supports this tag but then is not dealing with it correctly. If that's the case, you should file a bug against NeoVim. It should either report that it doesn't handle this tag or it should properly honor these hint diagnostics with the "Unnecessary" tag.

smackesey commented 3 years ago
  1. It is possible to use Pylance with other clients "off-label", all you have to do is download it and point your LSP client at the included dist/server.bundle.js`. I also use Pylance in VSCode (which I use for notebooks), so I just configured NeoVim/CoC to use the version downloaded through VSCode.

  2. That is interesting and useful information. I assumed it was information because that was the only other (i.e. not error or warning) category of diagnostic I saw listed in PyRight config docs, and it is showing up as a diagnostic. Including a screenshot here to show you what it looks like:

image

I will investigate why it is not showing as grayed-out and will post an update here when I figure it out. Perhaps it is a function of off-label use of Pylance, and if I just used PyRight it will show correctly.

arjenzorgdoc commented 3 years ago

(I'm responding to the title of this issue)

I like to name values I ignore:

def get_bar_v1(s: str):
    _foo, bar, _baz = s.split()
    return bar

def get_bar_v2(s: str):
    _, bar, _ = s.split()
    return bar

I think v1 is "better" than v2, but pyright doesn't agree:

  2:5 - error: Variable "_foo" is not accessed (reportUnusedVariable)
  2:16 - error: Variable "_baz" is not accessed (reportUnusedVariable)
erictraut commented 3 years ago

Symbol names that begin with underscores are treated as "private" variables. For example, if you declare a class variable:

class Foo:
    _foo: int = 3

The single underscore name is specifically reserved for variables that are unused but are placeholders.

If you don't like this convention, you can disable the reportUnusedVariable diagnostic rule in your project.

mike-kfed commented 3 years ago

I agree with @arjenzorgdoc his v1 is nicer, especially when you read foreign code you have an idea what the other split elements are supposed to be. Disabling reportUnusedVariable as suggested is not a good solution as the hint which variables are unused is actually good to have information. I'd vote for a dummy-variables-rgx config option like pylint has, which by default looks like (_+[a-zA-Z0-9]*?$). This is a long used practice in the python community, and therefore leading underscore is not just for pseudo-private class variables (they are actually accessible, it's just a naming convention). Having an ignore dummy variables pattern avoids noise in warnings/hints shown while helping keeping code clean.

bartenra commented 3 years ago

@mike-kfed That might already be implemented

In my case, I am defining callbacks which I am passing to a framework. Some of the arguments that the callback receives are not used.

This gives me a yellow squiggly line for both arguments:

def callback(parent: Any, info: Any) -> None: ...

This does not:

def callback(_parent: Any, _info: Any) -> None: ...

but on hover I still see the message "_info" is not accessed" (but it's not a warning).

mike-kfed commented 3 years ago

@bartenra I never claimed it is a warning, it is a hint, which is what you are seeing when you are hovering. The whole point made in this issue is, that this shouldn't be flagged by the linter at all when the variable name matches a certain agreed upon pattern, like other linters already support (not just for Python but other programming languages too, e.g. Rust analyser doesn't flag it when you use the underscore prefix). This would greatly help to keep the list of code-smells to fix limited to actual things that need fixing plus keep readability of code. I use vim, there I can get a list of all things found by my linters, which is convenient to use when improving code quality (given the linters only show report-worthy things). So no it has not been implemented as of today - and for some reason seems to be out of interest to ever be.

thotypous commented 3 years ago

Please reopen. This is a feature request, not a bug report.

erictraut commented 3 years ago

@thotypous, feel free to upvote (thumbs up) this issue if you are interested in it. We occasionally look at closed feature requests and reopen then if there is sufficient interest.

novasenco commented 3 years ago

I have a valid use-case. Inside of a script that serves as a plugin and is loaded externally (with importlib), every function I make is marked as "not accessed". For example,

from foo import command

def main():
    print("script loaded")

@command("PING")
def pong(server, message):
    server.send_pong(message.params[0])

# etc
# pong() is not called in any script directly, but rather it is loaded with importlib

Indeed, I was searching for a way to disable this diagnostic (on both a file level and on a per-function level). It's quite surprising it's not in and feels frustrating.

erictraut commented 3 years ago

You can disable any diagnostic on a file level by using a comment of the form:

# pyright: reportUnusedVariable=false

You can similarly change the severity of a diagnostic (e.g. from error to warning):

# pyright: reportUnusedVariable=warning

Of course, you can adjust the severity for (or completely disable) any diagnostic rule for your entire project if you don't like its behavior.

lucatpa commented 3 years ago

I'm facing this issue now, I think there is no easy way to avoid noise if you are just declaring functions to fulfill some interface but you only want to use some arguments for that function, not all of them. This will give you a HINT that you just have to learn to always ignore. It would be good to be able to disable this diagnostic on a per-function fashion.

Maybe an option can be added to the # pyright: marker, like (following the callback example):

# pyright: symbol=callback reportUnusedVariable=false
def callback(parent: Any, info: Any) -> None: ...

The comment could be also put at the start of the file, and will only activate that option for the symbol callback in the current file.

erictraut commented 3 years ago

The reportUnusedVariable check never generates diagnostics for parameters, so disabling it would have no effect. The hint generated for parameters is not a diagnostic. It's just a hint that tells the editor (if it supports such hints) to display the corresponding text in a subtle grayed-out appearance.

tshu-w commented 3 years ago

@erictraut So If there is an option to hidden these hints or don't show these hints when variables start with _ will be nice. On Emacs we can't distinguish between these hints and information diagnostics.

erictraut commented 3 years ago

I would consider that a bug in the LSP support for emacs. Please work with the maintainers of this functionality to address this limitation.

If the client (emacs or otherwise) specifies that it doesn't support hints, pyright will not generate them. So one option is to add a configuration flag to the emacs LSP support that disables this client capability.

jason0x43 commented 3 years ago

I've run into this issue pretty frequently as well. The problem is, as several others have pointed out, that the "is not accessed" hints end up creating a lot of noise. I know I can disable hints in the editor, but it seems heavy handed to have to disable all hints just to reduce the clutter generated by this one.

erictraut commented 3 years ago

As I said above, this is just a hint that should cause the editor to display the text in a "grayed out" fashion, giving you a subtle clue that the symbol isn't accessed. It is not meant to be an error or warning. If your editor is displaying it in some other (heavy-handed) way, then you should lobby for your editor maintainer to change it. It works great in VS Code.

lucatpa commented 3 years ago

This makes sense, I just brought it up to coc-pyright developers: https://github.com/fannheyward/coc-pyright/issues/107#issuecomment-887321347

lucatpa commented 3 years ago

Still, having a more fine control over which parameters you really want to ignore if they are unused (or at least at a function level) reduces the noise, because graying out is still noise if you know is fine, and the other way around, if I want to have noisy warnings about unused parameters in general, because in general it could be a problem just like unused variables, it's a shame that both cases gets mixed together (expected unused vs. overlooked unused).

jason0x43 commented 3 years ago

The issue (at least for me) isn't that hints are particularly obtrusive. My editor shows hints relatively subtly. However, when I open a file and it's full of any sort of indicator (and even hints have to be noticeable, or there wouldn't be much point to them), it's going to be distracting. Having a lot of unhelpful hints also makes it more difficult to notice those that are truly meaningful.

This is why the TypeScript compiler provides the underline pattern for function parameters, to let developers explicitly indicate that although a parameter is required to be in the signature (e.g., to match an interface definition), it isn't actually used in the method definition. When a function parameter is known to be required but unused, an editor ideally shouldn't even show a hint.

jakebailey commented 3 years ago

The underscore pattern works for TypeScript because parameter names don't matter in JavaScript. In Python, the name of a parameter is a part of the signature thanks to named parameters; if you change a parameter name to _foo, callers who have written foo=... will crash at runtime.

jason0x43 commented 3 years ago

True, the underscore pattern itself wouldn't be a great fit, but something analogous could work, like a comment, or even a flag to disable specifically the "unused function parameter" hint.

erictraut commented 3 years ago

The treatment (if done correctly) is meant to be subtle, similar to displaying different semantic classes of text in different colors. It's simply telling you that the symbol isn't referenced anywhere. It's not an error that you should feel compelled to fix. It sounds like in your mind it's something that needs to be addressed — that it's creating cognitive overhead when it's present. Is that the case? If so, then you're looking at it very differently than me (and I think most people).

jason0x43 commented 3 years ago

It's not an error that you should feel compelled to fix.

That is the root issue for me (and at least some others, given the activity in this issue). If the hints were only syntax highlights I could happily ignore them, but since they're LSP diagnostics they also show up when I look at the list of diagnostics for whatever file or project I'm looking at.

Certainly filtering the hints out of the diagnostics lists would be a very reasonable approach, and I'll look more into that. I do think it would also make sense for Pyright to have some way of marking intentionally unused arguments, because the hint loses much of its value when it can/should be ignored most of the time.

erictraut commented 3 years ago

They're not LSP diagnostics. If they are displayed by your editor as diagnostics, then that's the problem. This hint is supposed to be displayed as a syntax highlight.

jakebailey commented 3 years ago

That is the root issue for me (and at least some others, given the activity in this issue). If the hints were only syntax highlights I could happily ignore them, but since they're LSP diagnostics they also show up when I look at the list of diagnostics for whatever file or project I'm looking at.

I think that this is the key thing; diagnostics are the only delivery mechanism for this in the LSP, and that's why there is the hint level + the diagnostic tags in the spec. IIRC, if the client doesn't support tags, we don't try to emit these types of diagnostics to avoid sending "real" diagnostics, only visual hints. It's when these are interpreted in the wrong way that it gets bad (and we see this in other places like VS Code's Error Lens extension, which by default shows all hint diagnostics as text).

jason0x43 commented 3 years ago

I'm not sure why the tag support is relevant. I mean, having an "Unnecessary" tag means that the code in question is unnecessary/unused, but it doesn't imply that the diagnostic isn't a real diagnostic (at least, I wouldn't assume that). It may not be an "error", but it's something. If I put a return statement in the middle of a function, the rest of the function will end up with a similar diagnostic (hint level + unnecessary tag), but I most likely really care about that one.

lucatpa commented 3 years ago

This is why the TypeScript compiler provides the underline pattern for function parameters

Yeah, in python there is a special case for a single _ too (if you have only one unused parameter that is not a keyword-only parameter, you can just name it _ to avoid the unused hint). This is why it would also make sense to use something like _param or __param to indicate knowingly unused params too (this will even avoid the noisy of having to hint pyright with a comment).

lucatpa commented 3 years ago

The treatment (if done correctly) is meant to be subtle, similar to displaying different semantic classes of text in different colors. It's simply telling you that the symbol isn't referenced anywhere. It's not an error that you should feel compelled to fix. It sounds like in your mind it's something that needs to be addressed — that it's creating cognitive overhead when it's present. Is that the case? If so, then you're looking at it very differently than me (and I think most people).

If you are replying to me, then no, this is not what I am saying. You are ignoring the opposite case: unused function parameters I want to be noisily warned about. You are saying it is fine to have an ignorable subtle hint that a parameter is not used. I acknowledge (and agree) this is useful (this is why I asked coc-pyright authors to follow this guideline about hints that should not generate any diagnostics), but somehow you seem to be ignoring the inverse case, which is not possible to achieve currently and IMHO is useful to have.

erictraut commented 3 years ago

@lucatpa, I think I understand what you're saying. Currently, the reportUnusedVariable diagnostic never generates a diagnostic for an unused function parameter, but you want an option to generate an actual diagnostic in this case. And then you want a way to silence it in all the cases where an unused parameter is intended. Do I have that right?

The problem is that there are many legitimate cases in Python where a function parameter is unused, so this would be incredibly noisy. Plus, there's no legitimate mechanism in Python to eliminate the use of these parameters. As Jake mentioned above, you cannot rename them because parameter names are part fo the external contract for the function. So you'd need to use a # type: ignore comment or something equivalent to silence every one of these diagnostics. That doesn't sound like a good tradeoff. A typical code base would be littered with this.

For comparison, the TypeScript compiler offers a separate diagnostic check specifically for this case (noUnusedParameters), but in the case of TypeScript, you can simply delete these unused parameters (if they are at the end of the signature) or rename them (if they are not). In Python, these tricks don't work because Python enforces the arity (number of arguments), and parameter names are used when called with keyword arguments.

fannheyward commented 3 years ago

They're not LSP diagnostics.

@erictraut hello, here is a test code:

#!/usr/bin/env python3

def foo(name):
    """docstring for foo"""
    pass

The name parameter is not used, Pyright published it as a diagnostic:

{
    "range": {
        "start": {
            "line": 2,
            "character": 8
        },
        "end": {
            "line": 2,
            "character": 12
        }
    },
    "message": "\"name\" is not accessed",
    "severity": 4,
    "source": "Pyright",
    "tags": [
        1
    ]
}

coc can gray out it base on the tags, but it's still a diagnostic. In VSCode, it's a diagnostic too:

截屏2021-07-29 上午10 49 26
erictraut commented 3 years ago

Pyright published this as a hint with an "unnecessary" tag. While this is technically a diagnostic, it's not meant to be presented in the same way as an error, warning, or info diagnostic. If your editor is presenting it as such, then I would consider that a bug.

fannheyward commented 3 years ago

Thank you @erictraut, got your point now. Yes, the editor should display hint diagnostic in a better way.

lucatpa commented 3 years ago

@lucatpa, I think I understand what you're saying. Currently, the reportUnusedVariable diagnostic never generates a diagnostic for an unused function parameter, but you want an option to generate an actual diagnostic in this case. And then you want a way to silence it in all the cases where an unused parameter is intended. Do I have that right?

Yes, this would be the ideal for me.

you cannot rename them because parameter names are part fo the external contract for the function

I know this is not a general solution, but it does work when arguments come before a / for example, and in many other practical cases too.

So you'd need to use a # type: ignore comment or something equivalent to silence every one of these diagnostics. That doesn't sound like a good tradeoff. A typical code base would be littered with this.

Well, I have a different experience in this regard, I only have very few places where I have unused parameters (basically only when needing to implement and external interface and there are some parameters I don't use).

I'm not familiar enough with language servers but if one could configure the default, then it wouldn't be a problem. I don't even care if the default is not to generate any diagnostics like now as long as I configure my projects to do that (and have a mechanism to change the default for particular cases).

erictraut commented 3 years ago

We could theoretically add a new check (that is off by default), but our general philosophy is that we add new checks and corresponding configuration settings only when we have a good signal from users that it's a feature that is generally valuable, not a feature that just a handful of users want. For the reasons I mentioned above, I think it's unlikely that this would be useful in most code bases. We can revisit this decision if we get some strong signal back from pyright users.

lucatpa commented 3 years ago

Sounds reasonable, although I wonder if you use any metrics to asses how many users are interested in a particular feature or if you handle it simply as a hunch. Thanks!

erictraut commented 3 years ago

We use issues and discussions in the pyright and pylance-release repos. If you're interested in a particular feature, even if the issue has been closed, upvote it. We frequently review upvotes to see if there are decisions we should revisit.

lucatpa commented 3 years ago

Thanks! Upvoting meaning to add a :+1: reaction to the issue I guess?

holmanb commented 2 years ago

We could theoretically add a new check (that is off by default), but our general philosophy is that we add new checks and corresponding configuration settings only when we have a good signal from users that it's a feature that is generally valuable, not a feature that just a handful of users want. For the reasons I mentioned above, I think it's unlikely that this would be useful in most code bases. We can revisit this decision if we get some strong signal back from pyright users.

@erictraut Would a PR implementing this feature be welcome? Currently Pyright is unacceptably noisy on a codebase I contribute to (for reasons beyond my immediate control). My options appear to be:

  1. Find an alternative to pyright that supports this feature
  2. Contribute a fix to pyright
  3. Write a wrapper around pyright that filters out LSP RPCs matching the suggested syntax (leading underscore)

I'd really like to see a fix in pyright, but if a contribution wouldn't be welcome I'd lean towards options 1 or 3.

erictraut commented 2 years ago

@holmanb, if we implement this feature, I'd want to work with my colleagues on the design and implementation so it is consistent with other settings in pyright and pylance. A PR wouldn't help here.

I previously said that if the issue received a large number of upvotes, we would reconsider. Well, it has received quite a few upvotes, so I think it's worth revisiting.

The challenge is that there are a couple of different issues discussed above, so it's not clear to me which problem people are upvoting. I'd appreciate your help in clarifying your needs.

Issue 1: The "reportUnusedVariable" diagnostic check generates diagnostics for all unreferenced variables (with the exception of _), but there is a desire to exempt variables by name or pattern.

1a: Some want to exempt variable names that begin with a single underscore. 1b: Some want to exempt variable names based on a configurable list of regular expressions.

Issue 2: Pyright generates hints for unreferenced symbols if the client requests such hints. These hints are intended to be displayed as "grayed-out" text as a subtle cue that the symbol is not referenced. Some clients display these in a much less subtle way, making them appear as though they are errors in the code.

For issue 2, I think that's a client problem. If that is the issue, I recommend bringing it up with the maintainer of your client. If a client requests these hints it should either display them in an appropriate manner or provide a setting to disable these hints.

For issue 1, I'd like to understand whether 1a meet your needs. I recognize that 1b provides more flexibility, but it also adds more complexity and involves a new setting that needs to be maintained. Pyright and Pylance don't currently expose any reg-ex based settings, so 1b would be an anomaly. It would require more discussion and consideration.

Maybe the best approach is to start with solution 1a and see if that addresses the needs of most. Thoughts?

holmanb commented 2 years ago

@holmanb, if we implement this feature, I'd want to work with my colleagues on the design and implementation so it is consistent with other settings in pyright and pylance. A PR wouldn't help here.

Understood. Thanks for the response.

For issue 2, I think that's a client problem. If that is the issue, I recommend bringing it up with the maintainer of your client. If a client requests these hints it should either display them in an appropriate manner or provide a setting to disable these hints.

No objections.

For issue 1, I'd like to understand whether 1a meet your needs. I recognize that 1b provides more flexibility, but it also adds more complexity and involves a new setting that needs to be maintained. Pyright and Pylance don't currently expose any reg-ex based settings, so 1b would be an anomaly. It would require more discussion and consideration.

1a meets my needs. I agree, user-facing regex would add complexity.

Maybe the best approach is to start with solution 1a and see if that addresses the needs of most. Thoughts?

I think starting with 1a would be a good start.

mike-kfed commented 2 years ago

Also voting for 1a, thanks! The regex config option is just what the competition offers, e.g. pylint. But I feel that complexity is not needed, since the default regex is "prefix of underscore" and other linters of other programming languages also use that default e.g. Rust.

arjennienhuis commented 2 years ago

Also voting for 1a.

I don't need an extra setting for my use case. If the need ever arises we could still add it in a backwards compatible way.

lovetox commented 2 years ago

Also 1a