-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Running pyright on a full workspace does not seem to report all existing errors. #9642
Comments
Thanks for the detailed analysis. Here's a few additional questions:
|
Oh, I think I see what's going on here. You mentioned that My recommendation is that you don't attempt to manually split up the type checking for your repo. Just check the entire repo and use If you do split up type checking, you'll need to de-duplicate the resulting diagnostics yourself. You can do this by using the I'm going to close the issue because I'm pretty confident this explains the behaviors you're seeing, and pyright is working as intended here. If you think that I've missed something, feel free to reply. |
Thanks for the very prompt response, @erictraut .
We are using a sole
No.
No.
I run pyright at the root folder, and made sure all tests ran to gather the data for the issue report were run from the root folder.
The bulk of the errors reported only when checking files independently ( Here is the table summing grouping all errors in
Note: like in my previous reply, these runs check only files in Looking at the actual files from where the errors were reported shows 108 files had errors reported only when scanning independently. Here's a Venn diagram of the following data:
This is what ultimately made me run pyright on each file independently ( The large number of [Edit:] I found something interesting in 5 files:
|
Thanks for the additional details. Reopening because it does appear there's something unexpected going on here. |
I've tracked down one source of variability. This doesn't explain the full behaviour, only chips away at it. I was bugged by the variability found during the threads-test described in the opening issue. That is, when launching Hypothesis: there exist at least one file Experiment: for every file cd $PROJECT_ROOT
cat > pairwise_test.sh << EOF
echo -e "Testing with B=\$1\n\`pyright \$1 org/A.py\`"
EOF
find org/ -type f -name "*.py" | xargs -n 1 -P 14 bash pairwise_test.sh > pairwise_test.out Result: indeed, out of ~3600 pairwise-tested $ pyright org/A.py
0 errors, 0 warnings, 0 informations
$ pyright org/B.py
0 errors, 0 warnings, 0 informations
$ pyright org/B.py org/A.py
org/A.py
org/A.py:376:35 - error: Cannot access attribute "x" for class "Literal['data']"
Attribute "x" is unknown (reportAttributeAccessIssue)
org/A.py:376:35 - error: Cannot access attribute "x" for class "Literal['layout']"
Attribute "x" is unknown (reportAttributeAccessIssue)
org/A.py:376:35 - error: Cannot access attribute "x" for class "Literal['frames']"
Attribute "x" is unknown (reportAttributeAccessIssue)
org/A.py:376:60 - error: Cannot access attribute "xaxis" for class "Literal['data']"
Attribute "xaxis" is unknown (reportAttributeAccessIssue)
org/A.py:376:60 - error: Cannot access attribute "xaxis" for class "Literal['layout']"
Attribute "xaxis" is unknown (reportAttributeAccessIssue)
org/A.py:376:60 - error: Cannot access attribute "xaxis" for class "Literal['frames']"
Attribute "xaxis" is unknown (reportAttributeAccessIssue)
org/A.py:380:41 - error: Unnecessary "# type: ignore" comment (reportUnnecessaryTypeIgnoreComment)
org/A.py:396:39 - error: Cannot access attribute "y" for class "Literal['data']"
Attribute "y" is unknown (reportAttributeAccessIssue)
org/A.py:396:39 - error: Cannot access attribute "y" for class "Literal['layout']"
Attribute "y" is unknown (reportAttributeAccessIssue)
org/A.py:396:39 - error: Cannot access attribute "y" for class "Literal['frames']"
Attribute "y" is unknown (reportAttributeAccessIssue)
org/A.py:396:64 - error: Cannot access attribute "yaxis" for class "Literal['data']"
Attribute "yaxis" is unknown (reportAttributeAccessIssue)
org/A.py:396:64 - error: Cannot access attribute "yaxis" for class "Literal['layout']"
Attribute "yaxis" is unknown (reportAttributeAccessIssue)
org/A.py:396:64 - error: Cannot access attribute "yaxis" for class "Literal['frames']"
Attribute "yaxis" is unknown (reportAttributeAccessIssue)
org/A.py:403:45 - error: Unnecessary "# type: ignore" comment (reportUnnecessaryTypeIgnoreComment)
14 errors, 0 warnings, 0 informations Interestingly, inverting the order of the files in the pyright call results in no reported errors: $ pyright org/A.py org/B.py
0 errors, 0 warnings, 0 informations I assume when pyright typechecks Digging into the relationships between all Setup trigger and target: cat > B.trigger.py << EOF
import pandas as pd
import plotly.express as px
df = pd.DataFrame({"x_coord": [0, 1, 2, 3, 4], "y_coord": [2, 3, 4, 5, 1]})
fig = px.scatter(data_frame=df, x="x_coord", y="y_coord")
# The following line is enough to trigger the error
trigger = tuple(fig.data)
# Note that fig.data is already a touple, so this is not changing anything
assert trigger == fig.data
print(f"{(trigger == fig.data) = }")
EOF
cat > A.target.py << EOF
import pandas as pd
import plotly.graph_objs as go
import plotly.express as px
df = pd.DataFrame({"x_coord": [0, 1, 2, 3, 4], "y_coord": [2, 3, 4, 5, 1]})
fig = px.bar(df, x="x_coord", y="y_coord")
assert isinstance(fig.layout, go.Layout)
axes_data = [f.x for f in fig.data if f.xaxis == "x"]
print(f"{axes_data = }")
# The above code doesn't make much sense in this context, but I use it to
# replicate the original error. Accessing fig.data[0].xaxis will suffice
# to get the similar A.py/B.py effect, with a different error
# Note: I had to add the assert() in place of the original code, without it
# pyright will throw an error on this file alone. It is unclear to me why.
EOF Then test: $ python A.target.py
axes_data = [array([0, 1, 2, 3, 4])]
$ python B.trigger.py
(trigger == fig.data) = True
$ pyright A.target.py
0 errors, 0 warnings, 0 informations
$ pyright B.trigger.py
0 errors, 0 warnings, 0 informations
$ pyright A.target.py B.trigger.py
0 errors, 0 warnings, 0 informations
$ pyright B.trigger.py A.target.py
A.target.py
A.target.py:9:16 - error: Cannot access attribute "x" for class "Literal['data']"
Attribute "x" is unknown (reportAttributeAccessIssue)
A.target.py:9:16 - error: Cannot access attribute "x" for class "Literal['layout']"
Attribute "x" is unknown (reportAttributeAccessIssue)
A.target.py:9:16 - error: Cannot access attribute "x" for class "Literal['frames']"
Attribute "x" is unknown (reportAttributeAccessIssue)
A.target.py:9:41 - error: Cannot access attribute "xaxis" for class "Literal['data']"
Attribute "xaxis" is unknown (reportAttributeAccessIssue)
A.target.py:9:41 - error: Cannot access attribute "xaxis" for class "Literal['layout']"
Attribute "xaxis" is unknown (reportAttributeAccessIssue)
A.target.py:9:41 - error: Cannot access attribute "xaxis" for class "Literal['frames']"
Attribute "xaxis" is unknown (reportAttributeAccessIssue)
6 errors, 0 warnings, 0 informations
$ (I believe the above behaviour could go into its own issue, let me know if you want me to submit it separately.) A question still stands related to the main issue. If there are multiple
|
…type inference that involves recursion. This addresses #9642.
Thanks for the repro steps. I was able to repro the issue and determine the underlying cause. As you suspected, it was related to code within plotly. This library is unfortunately not annotated, so pyright needs to attempt to infer function and method return types. The problem occurs in the I've updated pyright's return type inference logic so it's more deterministic in cases like this. It's still not 100% deterministic because there are hard-coded limits on recursion depth that are required for performance and stability reasons (to prevent hangs or stack overflows). If one of these limits are hit, which should rarely if ever happen in non-contrived cases, it's still possible to observe order-dependent type evaluation phenomena like the one you reported. I'm going to ignore that edge case because I don't see a good way to address it. The issue you reported will be addressed in the next release. |
Thanks for the explanation on the source of that Just to clarify the "addressed in next version" flag relates only to the plotly's |
The next release will address the case that you highlighted in your repro. If you are able to repro other cases, please file separate bugs. |
OK, I'll submit other cases I find as separate bugs. It'd be great if this issue could stay open, since it has the main "workspace" vs "file" error differences which still highlights something weird happening with the way pyright is checking large codebases vs files. |
I leave issues open until the next release when the bug is addressed. If you find additional bugs, please open new issues. It's fine to reference this issue if you find that useful. |
Ugh, I just found a case of this sort:
At first sight it might seem to follow the same graph cycle problem found for plotly, so I will hold on until the next pyright release to see if it also fixed this. I will try to distill a repro then if the problem still exists. |
Describe the bug
I am seeing some puzzling behaviour in pyright where the number of errors reported varies depending on how checks are launched. That is, launching pyright on a workspace with ~6K files will miss errors that are found when the same files are checked in small batches or individually. In the tests below, I saw more than 360 errors reported when checking files independently that are missed when launching pyright on the workspace.
The expectation is that a typing error in a file would be reported regardless of whether that file was being checked independently, or as part of a larger list of files.
Code or Screenshots
I apologize in advance that I cannot share details of our code, but I will try my best to describe the symptoms and maybe get some insights in how to debug it further. (I also apologize for the long writeup, I had some time to kill...)
Context
We have a large monorepo with the bulk of code in two folders, let's call them
org
andorg_dev
. There are some restrictions in importing relationships:org
can import fromorg
, notorg_dev
.org_dev
can import fromorg
andorg_dev
.org
andorg_dev
import from third party libraries (core python, pandas, numpy, etc).The number of python files in
org
andorg_dev
are 3600+ and 2400+ respectively.Originally we were running pyright on everything:
To speed things up we separted the run into each
org
andorg_dev
in parallel:We got our speed increase, but we also got more errors reported (bulk 216, split 107+123=230). When running pyright directly on the files with new errors reported, pyright indeed reported the errors. (Note: the high number of errors are there because I am testing on 1.1.391 for this report; with our running version, 1.1.365, we went from 0 errors to 20-ish.)
It would seem that some files were not being checked when running pyright on the whole codebase at once, and only checked when fewer files were being given to pyright. Is this expected? Does pyright have an upper limit on files it can check?
I then ran the checks with
--verbose --stats
and the stats report does suggest that all runs are inspecting the expected number of files (6175 = 3681+2494):Selecting a file with errors reported only when running on
org
, and inspecting how it appears in each run log shows something like:The above seems to suggest that the file is being checked (time of 38ms), but the report is not being output.
More experiments
To dig into this further, I ran three more experiments:
org
,org_dev
orboth
.--threads
(only onorg
tree).org
tree).Heap size test
Heap size
Our production heapsize is set to 8GB by using
NODE_OPTIONS="--max-old-space-size=8192"
. I ran the three checksboth
(org org_dev
),org
, andorg_dev
with heap sizes of 8192, 16384 and 24576. Interestingly,both
had a reduction of 6 errors, the rest went unchanged. That is, checkingorg
andorg_dev
separately always resulted in more errors reported than checking them both in one pyright run:Although heap size did account for the missing errors, it did make for some interesting plots of memory management:
Memory behaviour with 8GB heap size
Memory behaviour with 24GB heap size
Threads test
Threads
Code block for test
The results seem puzzling. With one thread, they are consistent at 107 errors reported; but when multithreading, no only do I get more reports than single threaded, but the number of reports can vary between runs! This is the output of the script above:
Here are some Venn diagrams showing the differences:
More threads result in more errors reported
In three runs, using one thread had consistent results.
Using five threads resulted in more errors reported with variability between runs.
Using 10 threads resulted in even more errors, but variability did not increase.
Check folders and files test
Further separating folders
Since scanning both
org
andorg_dev
gave slightly different results. I wanted to know whether splittingorg
into smaller chunks would have further effects.I did two runs, one launching pyright targetting each subdirectory of
org/
(40+ subdirs, "dirscan
") and one targetting every independent file in theorg/
tree (3500+ files, "filescan
").In short,
org
check reported more errors thandirscan
, butfilescan
reported even more errors. Here is a Venn diagram of the overlap:The large number of errors found with
filescan
seemed reminiscent of the run with 10 threads. Indeed, most of the errors are found by both, the 10 threads scan andfilescan
(although not all), here is the Venn diagram of the overlap:Conclusion
The Venn diagrams describing the overlap between
filescan
and 10 threads suggest that launching pyright on a workspace is indeed either failing to detect, or report (since--stats
does indicate a check time for underreported files), typing errors in the codebase.VS Code extension or command-line
The tests in this report were run using pyright command line version 1.1.391. Although similar behaviour was seen with previous versions.
The text was updated successfully, but these errors were encountered: