pylint-dev / pylint

It's not just a linter that annoys you!
https://pylint.readthedocs.io/en/latest/
GNU General Public License v2.0
5.26k stars 1.12k forks source link

Type inference failure #392

Open pylint-bot opened 9 years ago

pylint-bot commented 9 years ago

Originally reported by: Thanassis Tsiodras (BitBucket: ttsiodras, GitHub: @ttsiodras?)


Disappointed by typos in my code that trigger errors at runtime (related to sqlalchemy reflected types), I decided to check whether I can do something better on my own, using pylint to limit the damage.

The beginning was very promising:

# ==> db.py <==
class Users(object):
    __slots__ = ['name', '_name']

    def get_name(self):
        return self._name

    def set_name(self, value):
        self._name = value

    name = property(get_name, set_name, None, "The name property")

    @staticmethod
    def findByName(unused_name):
        return Users("test user")

    def __init__(self, value="Unknown"):
        self._name = value

# ==> test.py <==
from db import Users

def test():
    user = Users.findByName("foo")
    user.namea = "typo in the field name"
    print user.name
    print user._name

test()

This code mirrors a basic skeleton of the kind of code my code generator would create from an existing DB schema - in this case, the class Users mirrors a table that has a 'name' column. A findByName function would also search the DB (via SQL) and return instances of "Users". For this first test, it just returns one dummy instance of the class Users.

And ideed, pylint caught the typo inside test() (assigning to a non-existing field):

E:  6, 4: Assigning to attribute 'namea' not defined in class slots (assigning-non-slot)

So moving on from a simple test, I changed the implementation of findByName to mirror the actual loop that would exist in its place, when reading output from a real SQL query:

# ==> db.py <==
class Users(object):
    ....    
    @staticmethod
    def findByName(unused_name):
        results = []
        for unused in xrange(10):
            results.append(Users("test user"))
        return results

    def __init__(self, value="Unknown"):
        self._name = value

# ==> test.py <==
from db import Users

def test():
    for user in Users.findByName("foo"):
        user.namea = 1
        print user.name
        print user._name

test()

Pylint now fails to see the same error. It appears that the line "results = []" tells pylint that this is a list of "unknown type", and all checks go out the window after it. The fact that the list is appended with instances of Users doesn't seem to matter.

The same happens if I don't use a list, and instead just yield the instances back:

@staticmethod
def findByName(unused_name):
    for unused in xrange(10):
        yield Users("test user")

In the case of using a list, I will concede that in theory, a list can contain many types. A single yield point however, can't - this looks like a bug to me.

So, to conclude - Is there a way I can tell pylint that the findByName function returns a list of Users?


pylint-bot commented 9 years ago

Original comment by Claudiu Popa (BitBucket: PCManticore, GitHub: @PCManticore):


Hm, I believe that right now we can't infer modifications after flow constructs, such as appending items to a list in a for-loop. But it's something that we should fix.

clavedeluna commented 1 year ago

Can confirm this still occurs.