pylance-release icon indicating copy to clipboard operation
pylance-release copied to clipboard

"Not a known attribute" error

Open ibobak opened this issue 1 year ago • 12 comments

Type: Bug

Below is a piece of code and two screenshots: one from VS Code and the other one from PyCharm. It is shown that PyCharm detects members fine, while VS Code cannot:

from typing import Optional

# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        p = ListNode(0)
        current = p
        while list1 or list2:
            if list1:
                if list2:
                    if list2.val > list1.val:
                        current.next = list1
                        list1 = list1.next
                    else:
                        current.next = list2
                        list2 = list2.next
                else:
                    current.next = list1
                    list1 = list1.next
            else:
                if list2:
                    current.next = list2
                    list2 = list2.next

            current = current.next
        
        return p.next

image

image

Extension version: 2024.5.1 VS Code version: Code 1.89.1 (dc96b837cf6bb4af9cd736aa3af08cf8279f7685, 2024-05-07T05:16:23.416Z) OS version: Linux x64 6.4.6-060406-generic Modes:

System Info
Item Value
CPUs Intel(R) Xeon(R) CPU E5-2696 v4 @ 2.20GHz (88 x 1197)
GPU Status 2d_canvas: enabled
canvas_oop_rasterization: disabled_off
direct_rendering_display_compositor: disabled_off_ok
gpu_compositing: enabled
multiple_raster_threads: enabled_on
opengl: enabled_on
rasterization: enabled
raw_draw: disabled_off_ok
skia_graphite: disabled_off
video_decode: enabled
video_encode: disabled_software
vulkan: disabled_off
webgl: enabled
webgl2: enabled
webgpu: disabled_off
Load (avg) 1, 1, 1
Memory (System) 251.76GB (231.37GB free)
Process Argv --crash-reporter-id 27d42247-63fb-4d9e-9cb0-87d9974843dc
Screen Reader no
VM 50%
DESKTOP_SESSION ubuntu-xorg
XDG_CURRENT_DESKTOP Unity
XDG_SESSION_DESKTOP ubuntu-xorg
XDG_SESSION_TYPE x11
A/B Experiments
vsliv368:30146709
vspor879:30202332
vspor708:30202333
vspor363:30204092
vscoreces:30445986
vscod805:30301674
binariesv615:30325510
vsaa593:30376534
py29gd2263:31024239
c4g48928:30535728
azure-dev_surveyone:30548225
2i9eh265:30646982
962ge761:30959799
pythongtdpath:30769146
welcomedialog:30910333
pythonidxpt:30866567
pythonnoceb:30805159
asynctok:30898717
pythontestfixt:30902429
pythonregdiag2:30936856
pythonmypyd1:30879173
pythoncet0:30885854
h48ei257:31000450
pythontbext0:30879054
accentitlementst:30995554
dsvsc016:30899300
dsvsc017:30899301
dsvsc018:30899302
cppperfnew:31000557
dsvsc020:30976470
pythonait:31006305
chatpanelt:31048053
dsvsc021:30996838
9c06g630:31013171
pythoncenvpt:31049070
a69g1124:31058053
pythonprt:31056678
dwnewjupyter:31046869
26j00206:31048877

ibobak avatar May 26 '24 09:05 ibobak

@ibobak that's not what the error says. what the error says is current could be None and next is not an attribute of None. or are you saying pylance doesn't correctly narrow the type?

that said, I am not sure how current could be None since current.next seems can't ever be None.

let me tag @erictraut

that said, for now, you can add this

assert current.next is not None before current = current.next to get rid of that error.

heejaechang avatar May 27 '24 07:05 heejaechang

Pyright is working correctly here. This isn't a bug.

The while condition list1 or list2 will not narrow the type of either list1 or list2. Type narrowing is applied to individual symbols (either list1 or list2). It does not work for combinations of symbols. The narrowed type of one symbol cannot be conditioned on the type of another symbol (e.g. "the type of list1 is ListNode if the type of list2 is None and vice versa).

Because of this, the static analyzer detects a code path within the while body where current.next is not assigned a value. This code path is the implied else for the if list2 statement. By inspection, we know that this implied else is unreachable, but we can determine that only by taking into account the "entanglement" between the type of list1 and list2.

To assist the type analyzer in this case, you will need to add an assert. You can either add it in the location where @heejaechang suggested or you can add an explicit else block with an assert.

                if list2:
                    current.next = list2
                    list2 = list2.next
                else:
                    assert current.next is not None

@ibobak, if you're interested in understanding how type narrowing works in static type checkers, I recommend reading this documentation.

erictraut avatar May 27 '24 07:05 erictraut

@heejaechang you said "the error says is current could be None". First of all, it cannot: you can see two lines

    p = ListNode(0)
    current = p

from which it follows that current cannot be None.

Second, even if current could be None,
image

ibobak avatar May 27 '24 07:05 ibobak

image

ibobak avatar May 27 '24 08:05 ibobak

see this comment for why it is happening - https://github.com/microsoft/pylance-release/issues/5922#issuecomment-2132873806

heejaechang avatar May 27 '24 08:05 heejaechang

about PyCharm, I dont think PyCharm is doing any type narrowing or it might be doing but not as much/good as pyright/pylance.

for example, I tested very simple case such as

from typing import Union

class A: pass
class B: pass

def foo(a: Union[A, B]):
    if isinstance(a, A):
        reveal_type(a)
    else:
        reveal_type(a)

pylance/pyright correctly figure out type of a based on its context. but Pycharm shows the same type (the declared type - A | B) regardless of its context.

heejaechang avatar May 27 '24 08:05 heejaechang

I am the guy who tries to move from PyCharm to VS Code and prove that VS code is better - believe me. And I admire VS Code very much. What I want to achieve now this this: to help you make your product better than Pycharm.

I still don't understand this: image

look at my first screenshot and compare to this one. They both prove that Pylance is working non-deterministically.

ibobak avatar May 27 '24 11:05 ibobak

One hour passed, and look again:

image

ibobak avatar May 27 '24 11:05 ibobak

To summarize:

  • line 17 on the last screen: sometimes it is marked with error, but as soon as some hours pass - it is unmarked. Then an hour passes - it is again marked. We've got a proof of undeterministic behavior which depends just on time (but not on code)
  • lines 20, 23, 27: they should also be marked with error (because there is no difference from 17 and 30), however, they are not detected.

Note: the code above is a correct solution of one of leetcode problems. It is a valid code.

ibobak avatar May 27 '24 12:05 ibobak

Your pycharm screen shot indicates that pycharm's type analyzer thinks that the type of the next instance variable is Any, which means that it's not performing any type check in this case. That's not surprising. Pycharm's type analyzer is not really considered a full type checker, and it's not conformant with the Python typing spec.

Let's compare pyright's behavior to mypy, which is another popular type checker in the Python community. Mypy doesn't support type inference for unannotated parameters, so we need to add explicit type annotations for the ListNode.__init__ method. Once I add those missing annotations to your code and run mypy on your sample, mypy reports a type violation on the line current = current.next, just as pyright does. If pycharm's type checking logic were more complete, it would also report an error here.

The intermittent error that you're seeing on the earlier line (current.next = list) is unexpected and looks like it could be a bug in pyright's code flow analysis. If you'd like me to dig into that further, please file a new bug in the pyright project with the relevant details.

erictraut avatar May 27 '24 16:05 erictraut

By telling "on the earlier line" - which line number are you mentioning?

ibobak avatar May 27 '24 19:05 ibobak

Line 17 in your screen shot (the line with the red squiggle).

erictraut avatar May 27 '24 19:05 erictraut