We want to make contributing to this project as easy and transparent as possible.
Read this in other languages: Español
We actively welcome your pull requests.
Pyre runs two different test suites:
unit tests in OCaml cover the main binary (
pyre.bin); these tests are run via
make test after the sources have been configured. Please refer to the Getting Started page for more information on bootstrapping the process.
Python tests for the wrappers are run via
In order to accept your pull request, we need you to submit a CLA. You only need to do this once to work on any of Facebook's open source projects.
Complete your CLA here: https://code.facebook.com/cla. If you have any questions, please drop us a line at firstname.lastname@example.org.
You are also expected to follow the Code of Conduct, so please read that if you are a new contributor.
We use GitHub issues to track public bugs. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue.
We value consistent code. Please follow the style of the surrounding code. Useful rules of thumb for all languages are
We use the
Black code formatter for all Python files. You can install the latest release via
pip install black, and you run it over the client files as
black pyre-check/client. More information are available at: https://github.com/ambv/black
We use the
ocamlformat code formatter for all OCaml files. You can install the latest release via
opam install ocamlformat, and run it on changed files via
ocamlformat -i affectedFile.ml.
On a high level, Pyre goes through the following steps to when "pyre" is called from the command line:
Read a .pyre_configuration to determine which source roots to analyze, as well as which python packages to analyze annotations for, and which pyre command to run. This information is used to determine which flags to pass into
pyre.bin, and shell out to the OCaml binary. The implementation of this step can be found under
Determine which pyre command to run. The commands include some which handle the lifetime and state of a persistent pyre server for a project, as well as a standalone run command called
pyre check. The implementation of these commands are found under
main.ml aggregates these commands, and handles the parsing of the command-line arguments. Most steps will eventually call the TypeCheckService.
The TypeCheckService module will call the ParseService, which will locate all sources in the given source root and all dependencies, and parse, preprocess & add the sources into shared memory. ParseService and TypeCheckService both live under
service/, whereas the parser itself is located in
parser/, and the preprocessor is under
analysis/preprocessing.ml. The AST (abstract syntax tree) that the source code is parsed into is specified in files under the
Next, pyre will populate the global type environment with the project's sources as well as that of the dependencies. Note that we don't recursively analyze the imports of the project's files, but instead add all sources to the environment at once. This choice makes it easier to parallelize the building of the type environment, but comes at the cost of not being able to depend on a file's dependencies being analyzed when adding it to the type environment. The type environment can be thought of as a collection of hash tables, mapping function, class, global, etc. names into their implementations that can be accessed from shared memory. The type order, which is explained in more detail in a section below, is also built here.
The modules that do the heavy lifting here can be found under
During analysis, each function will be processed into a control flow graph to represent the flow of typing information (https://en.wikipedia.org/wiki/Control_flow_graph is a nice introduction to CFG's). The bird's eye view of the algorithm is that we initialize the analysis with the type information from the function's parameter, and follow the control flow of the function to annotate local variables that are encountered. When encountering an attribute access, call, etc., the propagated type information is checked against the already present signature, and an error is generated if the two aren't compatible. The
Abstract Interpretation section provides a theoretical background for the analysis.
The module that handles all of the type checking analysis is at
analysis/typeCheck.ml. You can find the logic building the CFG and the fixpoint that runs the type checking analysis over the CFG at
analysis/fixpoint.ml, though the contents of these modules rarely change.
pyre check, all errors will be reported to stdout.
opam switch install "4.10.2"
maketo build the Pyre binary
Alternatively, you can also set up using the Docker image to build from source.
When you run pyre on the command line, what runs under the hood is a shim which finds and runs a suitable pyre binary. This works well for production use, but isn't great for testing out your own build of Pyre.
The way you can get around this is by setting up environment variables. If set,
PYRE_BINARY will override any binary in a configuration file and use the OCaml binary you've provided as
If you're working with a
PYRE_BINARY and are frequently re-compiling Pyre, you should avoid using the Pyre server, as the server will not stop when you re-compile Pyre. You should instead run
pyre check to create a one-off run.
Since OCaml doesn't have a great debugger at the time of writing of this article, we rely quite a bit on print debugging. In order to get persistent messages instead of having the preceding line get deleted, you should run
pyre --noninteractive check. You can introduce your own debugging messages into the code via
Log.dump. Most Pyre data structures automatically derive pretty printing functions, and you can use this in order to debug. Example:
let function_that_takes_expression (expression: Expression.t) = ..."Expression at this point: %s" (Expression.show expression); Log.dump rest_of_statements
In general, if you have a value of type
Module.show will be a function of type
t -> string that can be used for debugging purposes. If you're hoping to understand Pyre's state at a particular point in Python code, you can use the
reveal_type functions. Example:
# a.py def foo(x: typing.Optional[int]) -> None: # dumps the exit state and annotations of variables pyre_dump() # Prints out the control flow graph for the function pyre_dump_cfg() # dumps AST as JSON, with locations attached pyre_dump_locations() if x is not None: # Reveals the type of x at this point reveal_type(x) else: return x
Assuming that the test you're interested in running is
OUNIT_SHARDS="1" dune exec analysis/test/integration/methodTest.exe
If you see strange looking lookup errors, it may be because you forgot to set the shards to 1, which can mess up tests that rely on a scratch project in shared memory.
By contributing to Pyre, you agree that your contributions will be licensed under the LICENSE file in the root directory of this source tree.