Skip to content

Incorrect subdiagnostic suggestions for unresolved-reference diagnostics #584

@AlexWaygood

Description

@AlexWaygood

Summary

astral-sh/ruff#18444 added fantastic subdiagnostic suggestions for unresolved-reference diagnostics in methods. They work great for most cases, but for several edge cases they result in incorrect suggestions.


We should not add the subdiagnostic if the method is a staticmethod: the subdiagnostic does not make much sense here:

class Foo:
    def __init__(self):
        self.x = 42

    @staticmethod
    def static_method():
        print(x)

Our diagnostic is currently:

error[unresolved-reference]: Name `x` used when not defined
  --> foo.py:9:15
   |
 7 |     @staticmethod
 8 |     def static_method():
 9 |         print(x)
   |               ^
10 |
11 |     @classmethod
   |
info: An attribute `x` is available: consider using `self.x`
info: rule `unresolved-reference` is enabled by default

We should not suggest attributes only available on instances if the method is a classmethod: the subdiagnostic does not make much sense here:

class Foo:
    def __init__(self):
        self.x = 42

    @classmethod
    def class_method(cls):
        print(x)

Our diagnostic is currently:

error[unresolved-reference]: Name `x` used when not defined
 --> bar.py:7:15
  |
5 |     @classmethod
6 |     def class_method(cls):
7 |         print(x)
  |               ^
  |
info: An attribute `x` is available: consider using `self.x`
info: rule `unresolved-reference` is enabled by default

This should be fixed by using SubclassOf::from(self.db(), class.default_specialization(self.db())) here if it's a classmethod (but still using Type::instance(self.db(), class.default_specialization(self.db())) if it's an instance method).


The subdiagnostic message always says "consider using self.<attribute>". But we should inspect the function to check what the name of the first parameter is rather than assuming it's self. Even for instance methods, the name of the first parameter is often not self if it's an instance method on a metaclass, for example:

class Foo:
    Y = 42

    @classmethod
    def class_method(cls):
        print(Y)


class Meta(type):
    Z = 42
    
    def instance_metaclass_method(cls):
        print(Z)

Our current diagnostics:

error[unresolved-reference]: Name `Y` used when not defined
 --> baz.py:6:15
  |
4 |     @classmethod
5 |     def class_method(cls):
6 |         print(Y)
  |               ^
  |
info: An attribute `Y` is available: consider using `self.Y`
info: rule `unresolved-reference` is enabled by default

error[unresolved-reference]: Name `Z` used when not defined
  --> baz.py:13:15
   |
12 |     def instance_metaclass_method(cls):
13 |         print(Z)
   |               ^
   |
info: An attribute `Z` is available: consider using `self.Z`
info: rule `unresolved-reference` is enabled by default

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdiagnosticsRelated to reporting of diagnostics.help wantedContributions especially welcome

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions