[
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\njobs:\n  check:\n    name: Check\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v2\n\n      - name: Install stable toolchain\n        uses: actions-rs/toolchain@v1\n        with:\n          profile: minimal\n          toolchain: stable\n          override: true\n\n      - name: Run cargo check\n        uses: actions-rs/cargo@v1\n        with:\n          command: check\n\n  test:\n    name: Test Suite\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v2\n\n      - name: Install stable toolchain\n        uses: actions-rs/toolchain@v1\n        with:\n          profile: minimal\n          toolchain: stable\n          override: true\n\n      - name: Run cargo test\n        uses: actions-rs/cargo@v1\n        with:\n          command: test\n\n  lints:\n    name: Lints\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout sources\n        uses: actions/checkout@v2\n\n      - name: Install stable toolchain\n        uses: actions-rs/toolchain@v1\n        with:\n          profile: minimal\n          toolchain: stable\n          override: true\n          components: rustfmt, clippy\n\n      - name: Run cargo fmt\n        uses: actions-rs/cargo@v1\n        with:\n          command: fmt\n          args: --all -- --check\n\n      - name: Run cargo clippy\n        uses: actions-rs/cargo@v1\n        with:\n          command: clippy\n          args: -- -D warnings\n"
  },
  {
    "path": ".gitignore",
    "content": ".vscode\n/target\n/examples\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: local\n    hooks:\n      - id: rustfmt\n        name: rustfmt\n        description: Check Rust source code formatting using cargo fmt\n        entry: cargo fmt --all -- --check --color always\n        language: system\n        pass_filenames: false\n"
  },
  {
    "path": ".rustfmt.toml",
    "content": "fn_params_layout = \"Tall\"\nuse_small_heuristics = \"Max\"\nmax_width = 100\nreorder_modules = false\nreorder_imports = false\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Release Notes\n\n## v0.8.1 (2023-03-21)\n\n-   Updated dependencies flagged by cargo-audit.\n\n## v0.8.0 (2023-03-21)\n\n### Features\n\n-   Circomspect will now only report findings for potential issues in the files\n    specified on the command line. (It will still attempt to parse included\n    files, but these will only be used to inform the analysis of the files\n    specified by the user.)\n-   Added support for tags, tuples, and anonymous components. Circomspect now\n    supports Circom versions 2.0.0 - 2.1.4.\n-   Added templates to the `bn254-specific-circuits` analysis pass.\n-   Added `unused-output-signal` analysis pass.\n-   All uses of the name BN128 have been replaced with BN254.\n\n### Bug fixes\n\n-   Rewrote the `unconstrained-less-than` analysis pass to better capture the\n    underlying issue.\n-   Fixed an issue where the cyclomatic complexity calculation could underflow\n    in some cases in the `overly-complex-function-or-template` analysis pass.\n-   Fixed an issue in the Sarif export implementation where reporting\n    descriptors were added multiple times.\n\n## v0.7.2 (2022-12-01)\n\n### Features\n\n-   Added a URL to the issue description for each output.\n\n### Bug Fixes\n\n-   Rewrote description of the unconstrained less-than analysis pass, as the\n    previous description was too broad.\n-   Fixed grammar in the under-constrained signal warning message.\n\n## v0.7.0 (2022-11-29)\n\n### Features\n\n-   New analysis pass (`unconstrained-less-than`) that detects uses of the\n    Circomlib `LessThan` template where the input signals are not constrained\n    to be less than the bit size passed to `LessThan`.\n-   New analysis pass (`unconstrained-division`) that detects signal\n    assignments containing division, where the divisor is not constrained to be\n    non-zero.\n-   New analysis pass (`bn254-specific-circuits`) that detects uses of\n    Circomlib templates with hard-coded BN254-specific constants together with\n    a custom curve like BLS12-381 or Goldilocks.\n-   New analysis pass (`under-constrained-signal`) that detects intermediate\n    signals which do not occur in at least two separate constraints.\n-   Rule name is now included in Sarif output. (The rule name is now also\n    displayed by the VSCode Sarif extension.)\n-   Improved parsing error messages.\n\n### Bug Fixes\n\n-   Fixed an issue during value propagation where values would be propagated to\n    arrays by mistake.\n-   Fixed an issue in the `nonstrict-binary-conversion` analysis pass where\n    some instantiations of `Num2Bits` and `Bits2Num` would not be detected.\n-   Fixed an issue where the maximum degree of switch expressions were\n    evaluated incorrectly.\n-   Previous versions could take a very long time to complete value and degree\n    propagation. These analyses are now time boxed and will exit if the\n    analysis takes more than 10 seconds to complete.\n"
  },
  {
    "path": "CODEOWNERS",
    "content": "* @fegge\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nresolver = \"1\"\n\nmembers = [\n  \"cli\",\n  \"parser\",\n  \"program_analysis\",\n  \"program_structure\",\n  \"program_structure_tests\",\n]\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2021 0Kims Association <https://0kims.org>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    {one line to give the program's name and a brief idea of what it does.}\n    Copyright (C) {year}  {name of author}\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    {project}  Copyright (C) {year}  {fullname}\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "# Circomspect 🔎\n\n![Crates.io badge](https://img.shields.io/crates/v/circomspect.svg) ![GitHub badge](https://github.com/trailofbits/circomspect/actions/workflows/ci.yml/badge.svg)\n\nCircomspect is a static analyzer and linter for the [Circom](https://iden3.io/circom) programming language. The codebase borrows heavily from the Rust Circom compiler built by [iden3](https://github.com/iden3).\n\nCircomspect currently implements a number of analysis passes which can identify potential issues in Circom circuits. It is our goal to continue to add new analysis passes to be able to detect more issues in the future.\n\n![Circomspect example image](https://github.com/trailofbits/circomspect/raw/main/doc/circomspect.png)\n\n## Installing Circomspect\n\nCircomspect is available on [crates.io](https://crates.io/crates/circomspect) and can be installed by invoking\n\n```sh\n  cargo install circomspect\n```\n\nTo build Circomspect from source, simply clone the repository and build the\nproject by running `cargo build` in the project root. To install from source, use\n\n```sh\n  cargo install --path cli\n```\n\n## Running Circomspect\n\nTo run Circomspect on a file or directory, simply run\n\n```sh\n  circomspect path/to/circuit\n```\n\nBy default, Circomspect outputs warnings and errors to stdout. To see informational results as well you can set the output level using the `--level` option. To ignore certain types of results, you can use the `--allow` option together with the corresponding result ID. (The result ID can be obtained by passing the `--verbose` flag to Circomspect.)\n\nTo output the results to a Sarif file (which can be read by the [VSCode Sarif Viewer](https://marketplace.visualstudio.com/items?itemName=MS-SarifVSCode.sarif-viewer)), use the option `--sarif-file`.\n\n![VSCode example image](https://github.com/trailofbits/circomspect/raw/main/doc/vscode.png)\n\nCircomspect supports the same curves that Circom does: BN254, BLS12-381, and Goldilocks. If you are using a different curve than the default (BN254) you can set the curve using the command line option `--curve`.\n\n## Analysis Passes\n\nCircomspect implements analysis passes for a number of different types of issues. A complete list, together with a high-level description of each issue, can be found [here](https://github.com/trailofbits/circomspect/blob/main/doc/analysis_passes.md).\n"
  },
  {
    "path": "TODO.md",
    "content": "# TODO\n\n  - [x] Implement a basic block type, and functionality allowing us to lift the\n        AST to a CFG.\n  - [x] Implement `vars_read`, `vars_written`, `signals_read`,\n        `signals_written`, and `signals_constrained` on `Statement`.\n  - [x] Compute dominators, dominator frontiers, and immediate dominators on\n        basic blocks. (See _A Simple, Fast Dominance Algorithm_.)\n  - [x] Implement (pruned) SSA.\n  - [ ] Implement analyses enabled by SSA:\n      - [x] Constant propagation\n          - [x] Implement constant propagation.\n          - [x] Implement/update `is_constant` and `value` on `Expression`.\n      - [x] Dead code analysis\n      - [ ] Value-range analysis (simple overflow detection)\n      - [x] Intraprocedural data flow\n      - [x] Unconstrained signals (simple)\n  - [ ] Implement emulation.\n      - [ ] Unconstrained signals (specific)\n  - [ ] Implement symbolic execution.\n      - [ ] Unconstrained signals (complete)\n      - [ ] Overflow detection (complete)\n\n\n# Potential issues\n\n - [x] Bit level arithmetic does not commute with modular reduction. This means that\n     - Currently, `(p | 1) - 1 != 0` (see `circom_algebra/src/modular_arithmetic.rs`)\n     - `!x` (256-bit complement) will typically overflow which means that `!x`\n       does not satisfy `(!x)_i = x_i ^ 1` for all `i`.\n\n - [x] Arithmetic is done in `(p/2, p/2]` which may produce unexpected results.\n     - E.g. `p/2 + 1 < p/2 - 1`.\n\n - [ ] Typically you want to constrain all input and output signals for each\n       instantiated component in each circuit. There are exceptions from this\n       rule (e.g. the circomlib `AliasCheck` template). We should add an\n       analysis pass ensuring that signals belonging to instantiated\n       subcomponents are properly constrained.\n\n - [ ] Find cases when it is possible to prove that the output from a component\n       is not uniquely determined by the input.\n"
  },
  {
    "path": "circom_algebra/Cargo.toml",
    "content": "[package]\nname = \"circomspect-circom-algebra\"\nversion = \"2.0.2\"\nedition = \"2021\"\nrust-version = \"1.65\"\nlicense = \"LGPL-3.0-only\"\nauthors = [\"hermeGarcia <hermegarcianavarro@gmail.com>\"]\ndescription = \"Support crate for the Circomspect static analyzer\"\nrepository = \"https://github.com/trailofbits/circomspect\"\n\n[dependencies]\nnum-bigint-dig = \"0.8\"\nnum-traits = \"0.2\"\n"
  },
  {
    "path": "circom_algebra/src/lib.rs",
    "content": "pub extern crate num_bigint_dig as num_bigint;\npub extern crate num_traits;\n\npub mod modular_arithmetic;\n"
  },
  {
    "path": "circom_algebra/src/modular_arithmetic.rs",
    "content": "use num_bigint::{BigInt, ModInverse, Sign};\nuse num_traits::ToPrimitive;\n\npub enum ArithmeticError {\n    DivisionByZero,\n    BitOverFlowInShift,\n}\n\nfn modulus(a: &BigInt, b: &BigInt) -> BigInt {\n    ((a % b) + b) % b\n}\n// The maximum number of bits a BigInt can have is 18_446_744_073_709_551_615\n// Returns the LITTLE ENDIAN representation of the bigint\nfn bit_representation(elem: &BigInt) -> (Sign, Vec<u8>) {\n    elem.to_radix_le(2)\n}\n// Computes 2**b -1 where b is the number of bits of field\nfn mask(field: &BigInt) -> BigInt {\n    let two = BigInt::from(2);\n    let b = bit_representation(field).1.len();\n    let mask = num_traits::pow::pow(two, b);\n    mask - 1\n}\n\n// Arithmetic operations\npub fn add(left: &BigInt, right: &BigInt, field: &BigInt) -> BigInt {\n    //let left = modulus(left,field);\n    //let right = modulus(right,field);\n    modulus(&(left + right), field)\n}\npub fn mul(left: &BigInt, right: &BigInt, field: &BigInt) -> BigInt {\n    //let left = modulus(left,field);\n    //let right = modulus(right,field);\n    modulus(&(left * right), field)\n}\npub fn sub(left: &BigInt, right: &BigInt, field: &BigInt) -> BigInt {\n    //let left = modulus(left,field);\n    //let right = modulus(right,field);\n    modulus(&(left - right), field)\n}\npub fn div(left: &BigInt, right: &BigInt, field: &BigInt) -> Result<BigInt, ArithmeticError> {\n    let right_inverse = right.mod_inverse(field).ok_or(ArithmeticError::DivisionByZero)?;\n    let res = mul(left, &right_inverse, field);\n    Ok(res)\n}\npub fn idiv(left: &BigInt, right: &BigInt, field: &BigInt) -> Result<BigInt, ArithmeticError> {\n    let zero = BigInt::from(0);\n    let left = modulus(left, field);\n    let right = modulus(right, field);\n    if right == zero {\n        Err(ArithmeticError::DivisionByZero)\n    } else {\n        Ok(left / right)\n    }\n}\npub fn mod_op(left: &BigInt, right: &BigInt, field: &BigInt) -> Result<BigInt, ArithmeticError> {\n    let left = modulus(left, field);\n    let right = modulus(right, field);\n    Ok(modulus(&left, &right))\n}\npub fn pow(base: &BigInt, exp: &BigInt, field: &BigInt) -> BigInt {\n    base.modpow(exp, field)\n}\npub fn prefix_sub(elem: &BigInt, field: &BigInt) -> BigInt {\n    let minus_one = BigInt::from(-1);\n    mul(elem, &minus_one, field)\n}\n\n//Bit operations\n\n// 256 bit complement\npub fn complement_256(elem: &BigInt, field: &BigInt) -> BigInt {\n    let (sign, mut bit_repr) = bit_representation(elem);\n    while bit_repr.len() > 256 {\n        bit_repr.pop();\n    }\n    for _i in bit_repr.len()..256 {\n        bit_repr.push(0);\n    }\n    for bit in &mut bit_repr {\n        *bit = u8::from(*bit == 0);\n    }\n    let cp = BigInt::from_radix_le(sign, &bit_repr, 2).unwrap();\n    modulus(&cp, field)\n}\n\npub fn shift_l(left: &BigInt, right: &BigInt, field: &BigInt) -> Result<BigInt, ArithmeticError> {\n    let two = BigInt::from(2);\n    let top = field / &two;\n    if right <= &top {\n        let usize_repr = right.to_usize().ok_or(ArithmeticError::DivisionByZero)?;\n        let value = modulus(&((left * &num_traits::pow(two, usize_repr)) & &mask(field)), field);\n        Ok(value)\n    } else {\n        shift_r(left, &(field - right), field)\n    }\n}\npub fn shift_r(left: &BigInt, right: &BigInt, field: &BigInt) -> Result<BigInt, ArithmeticError> {\n    let two = BigInt::from(2);\n    let top = field / &two;\n    if right <= &top {\n        let usize_repr = right.to_usize().ok_or(ArithmeticError::DivisionByZero)?;\n        let value = left / &num_traits::pow(two, usize_repr);\n        Ok(value)\n    } else {\n        shift_l(left, &(field - right), field)\n    }\n}\npub fn bit_or(left: &BigInt, right: &BigInt, field: &BigInt) -> BigInt {\n    modulus(&(left | right), field)\n}\npub fn bit_and(left: &BigInt, right: &BigInt, field: &BigInt) -> BigInt {\n    modulus(&(left & right), field)\n}\npub fn bit_xor(left: &BigInt, right: &BigInt, field: &BigInt) -> BigInt {\n    modulus(&(left ^ right), field)\n}\n\n// Boolean operations\nfn constant_true() -> BigInt {\n    BigInt::from(1)\n}\nfn constant_false() -> BigInt {\n    BigInt::from(0)\n}\nfn val(elem: &BigInt, field: &BigInt) -> BigInt {\n    let c = (field / &BigInt::from(2)) + 1;\n    if &c <= elem && elem < field {\n        elem - field\n    } else {\n        elem.clone()\n    }\n}\nfn comparable_element(elem: &BigInt, field: &BigInt) -> BigInt {\n    val(&modulus(elem, field), field)\n}\nfn normalize(elem: &BigInt, field: &BigInt) -> BigInt {\n    let f = constant_false();\n    let t = constant_true();\n    if comparable_element(elem, field) == f {\n        f\n    } else {\n        t\n    }\n}\npub fn as_bool(elem: &BigInt, field: &BigInt) -> bool {\n    normalize(elem, field) != constant_false()\n}\npub fn not(elem: &BigInt, field: &BigInt) -> BigInt {\n    (normalize(elem, field) + 1) % 2\n}\npub fn bool_or(left: &BigInt, right: &BigInt, field: &BigInt) -> BigInt {\n    (normalize(left, field) + normalize(right, field) + bool_and(left, right, field)) % 2\n}\npub fn bool_and(left: &BigInt, right: &BigInt, field: &BigInt) -> BigInt {\n    normalize(left, field) * normalize(right, field)\n}\npub fn eq(left: &BigInt, right: &BigInt, field: &BigInt) -> BigInt {\n    let left = modulus(left, field);\n    let right = modulus(right, field);\n    if left == right {\n        constant_true()\n    } else {\n        constant_false()\n    }\n}\npub fn lesser(left: &BigInt, right: &BigInt, field: &BigInt) -> BigInt {\n    let left = comparable_element(left, field);\n    let right = comparable_element(right, field);\n    if left < right {\n        constant_true()\n    } else {\n        constant_false()\n    }\n}\npub fn not_eq(left: &BigInt, right: &BigInt, field: &BigInt) -> BigInt {\n    not(&eq(left, right, field), field)\n}\npub fn lesser_eq(left: &BigInt, right: &BigInt, field: &BigInt) -> BigInt {\n    bool_or(&lesser(left, right, field), &eq(left, right, field), field)\n}\npub fn greater(left: &BigInt, right: &BigInt, field: &BigInt) -> BigInt {\n    not(&lesser_eq(left, right, field), field)\n}\npub fn greater_eq(left: &BigInt, right: &BigInt, field: &BigInt) -> BigInt {\n    bool_or(&greater(left, right, field), &eq(left, right, field), field)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    const FIELD: &str = \"257\";\n    #[test]\n    fn mod_check() {\n        let a = BigInt::from(-8);\n        let b = BigInt::from(5);\n        let res = super::modulus(&a, &b);\n        assert_eq!(res, BigInt::from(2));\n    }\n    #[test]\n    fn comparison_check() {\n        let field = BigInt::parse_bytes(FIELD.as_bytes(), 10)\n            .expect(\"generating the big int was not possible\");\n        let a = sub(&BigInt::from(2), &BigInt::from(1), &field);\n        let b = BigInt::from(-1);\n        let res = not_eq(&a, &b, &field);\n        assert!(as_bool(&res, &field));\n    }\n    #[test]\n    fn mod_operation_check() {\n        let field = BigInt::parse_bytes(FIELD.as_bytes(), 10)\n            .expect(\"generating the big int was not possible\");\n        let a = BigInt::from(17);\n        let b = BigInt::from(32);\n        if let Ok(res) = mod_op(&a, &b, &field) {\n            assert_eq!(a, res)\n        } else {\n            unreachable!();\n        }\n    }\n    #[test]\n    fn complement_of_complement_is_the_original_test() {\n        let field = BigInt::parse_bytes(FIELD.as_bytes(), 10)\n            .expect(\"generating the big int was not possible\");\n        let big_num = BigInt::parse_bytes(\"1234\".as_bytes(), 10)\n            .expect(\"generating the big int was not possible\");\n        let big_num_complement = complement_256(&big_num, &field);\n        let big_num_complement_complement = complement_256(&big_num_complement, &field);\n        let big_num_modulus = modulus(&big_num, &field);\n        assert_eq!(big_num_complement_complement, big_num_modulus);\n    }\n    #[test]\n    fn lesser_eq_test() {\n        let field = BigInt::parse_bytes(FIELD.as_bytes(), 10)\n            .expect(\"generating the big int was not possible\");\n        let zero = BigInt::from(0);\n        let two = BigInt::from(2);\n        assert!(zero < two);\n        assert!(as_bool(&lesser_eq(&zero, &two, &field), &field));\n    }\n}\n"
  },
  {
    "path": "cli/Cargo.toml",
    "content": "[package]\nname = \"circomspect\"\nversion = \"0.9.0\"\nedition = \"2021\"\nrust-version = \"1.65\"\nlicense = \"LGPL-3.0-only\"\nauthors = [\"Trail of Bits\"]\nreadme = \"../README.md\"\ndescription = \"A static analyzer and linter for the Circom zero-knowledge DSL\"\nkeywords = [\"cryptography\", \"static-analysis\", \"zero-knowledge\", \"circom\"]\nrepository = \"https://github.com/trailofbits/circomspect\"\n\n[dependencies]\nanyhow = \"1.0\"\natty = \"0.2\"\nclap = { version = \"4.5\", features = [\"derive\"] }\nlog = \"0.4\"\nparser = { package = \"circomspect-parser\", version = \"2.1.3\", path = \"../parser\" }\npretty_env_logger = \"0.5\"\nprogram_analysis = { package = \"circomspect-program-analysis\", version = \"0.8.1\", path = \"../program_analysis\" }\nprogram_structure = { package = \"circomspect-program-structure\", version = \"2.1.3\", path = \"../program_structure\" }\nserde_json = \"1.0\"\ntermcolor = \"1.1\"\n"
  },
  {
    "path": "cli/src/main.rs",
    "content": "use std::collections::HashSet;\nuse std::path::PathBuf;\nuse std::process::ExitCode;\nuse clap::{CommandFactory, Parser};\n\nuse program_analysis::config;\nuse program_analysis::analysis_runner::AnalysisRunner;\n\nuse program_structure::constants::Curve;\nuse program_structure::file_definition::FileID;\nuse program_structure::report::Report;\nuse program_structure::report::MessageCategory;\nuse program_structure::writers::{LogWriter, ReportWriter, SarifWriter, CachedStdoutWriter};\n\n#[derive(Parser, Debug)]\n#[command(styles=cli_styles())]\n/// A static analyzer and linter for Circom programs.\nstruct Cli {\n    /// Initial input file(s)\n    #[clap(name = \"INPUT\")]\n    input_files: Vec<PathBuf>,\n\n    /// Library file paths\n    #[clap(short = 'L', long = \"library\", name = \"LIBRARIES\")]\n    libraries: Vec<PathBuf>,\n\n    /// Output level (INFO, WARNING, or ERROR)\n    #[clap(short = 'l', long = \"level\", name = \"LEVEL\", default_value = config::DEFAULT_LEVEL)]\n    output_level: MessageCategory,\n\n    /// Output analysis results to a Sarif file\n    #[clap(short, long, name = \"OUTPUT\")]\n    sarif_file: Option<PathBuf>,\n\n    /// Ignore results from given analysis passes\n    #[clap(short = 'a', long = \"allow\", name = \"ID\")]\n    allow_list: Vec<String>,\n\n    /// Enable verbose output\n    #[clap(short = 'v', long = \"verbose\")]\n    verbose: bool,\n\n    /// Set curve (BN254, BLS12_381, or GOLDILOCKS)\n    #[clap(short = 'c', long = \"curve\", name = \"NAME\", default_value = config::DEFAULT_CURVE)]\n    curve: Curve,\n}\n\n/// Styles the help output for the [`Cli`].\nfn cli_styles() -> clap::builder::Styles {\n    use clap::builder::styling::*;\n\n    Styles::styled()\n        .header(AnsiColor::Yellow.on_default())\n        .usage(AnsiColor::Green.on_default())\n        .literal(AnsiColor::Green.on_default())\n        .placeholder(AnsiColor::Green.on_default())\n}\n\n/// Returns true if a primary location of the report corresponds to a file\n/// specified on the command line by the user.\nfn filter_by_file(report: &Report, user_inputs: &HashSet<FileID>) -> bool {\n    report.primary_file_ids().iter().any(|file_id| user_inputs.contains(file_id))\n}\n\n/// Returns true if the report level is greater than or equal to the given\n/// level.\nfn filter_by_level(report: &Report, output_level: &MessageCategory) -> bool {\n    report.category() >= output_level\n}\n\n/// Returns true if the report ID is not in the given list.\nfn filter_by_id(report: &Report, allow_list: &[String]) -> bool {\n    !allow_list.contains(&report.id())\n}\n\nfn main() -> ExitCode {\n    // Initialize logger and options.\n    pretty_env_logger::init();\n    let options = Cli::parse();\n    if options.input_files.is_empty() {\n        match Cli::command().print_help() {\n            Ok(()) => return ExitCode::SUCCESS,\n            Err(_) => return ExitCode::FAILURE,\n        }\n    }\n\n    // Set up analysis runner.\n    let (mut runner, reports) = AnalysisRunner::new(options.curve)\n        .with_libraries(&options.libraries)\n        .with_files(&options.input_files);\n\n    // Set up writer and write reports to `stdout`.\n    let allow_list = options.allow_list.clone();\n    let user_inputs = runner.file_library().user_inputs().clone();\n    let mut stdout_writer = CachedStdoutWriter::new(options.verbose)\n        .add_filter(move |report: &Report| filter_by_level(report, &options.output_level))\n        .add_filter(move |report: &Report| filter_by_file(report, &user_inputs))\n        .add_filter(move |report: &Report| filter_by_id(report, &allow_list));\n    stdout_writer.write_reports(&reports, runner.file_library());\n\n    // Analyze functions and templates in user provided input files.\n    runner.analyze_functions(&mut stdout_writer, true);\n    runner.analyze_templates(&mut stdout_writer, true);\n\n    // If a Sarif file is passed to the program we write the reports to it.\n    if let Some(sarif_file) = options.sarif_file {\n        let allow_list = options.allow_list.clone();\n        let user_inputs = runner.file_library().user_inputs().clone();\n        let mut sarif_writer = SarifWriter::new(&sarif_file)\n            .add_filter(move |report: &Report| filter_by_level(report, &options.output_level))\n            .add_filter(move |report: &Report| filter_by_file(report, &user_inputs))\n            .add_filter(move |report: &Report| filter_by_id(report, &allow_list));\n        if sarif_writer.write_reports(stdout_writer.reports(), runner.file_library()) > 0 {\n            stdout_writer.write_message(&format!(\"Result written to `{}`.\", sarif_file.display()));\n        }\n    }\n\n    // Use the exit code to indicate if any issues were found.\n    match stdout_writer.reports_written() {\n        0 => {\n            stdout_writer.write_message(\"No issues found.\");\n            ExitCode::SUCCESS\n        }\n        1 => {\n            stdout_writer.write_message(\"1 issue found.\");\n            ExitCode::FAILURE\n        }\n        n => {\n            stdout_writer.write_message(&format!(\"{n} issues found.\"));\n            ExitCode::FAILURE\n        }\n    }\n}\n"
  },
  {
    "path": "doc/analysis_passes.md",
    "content": "# Analysis Passes\n\n### Side-effect free assignment\n\nAn assigned value which does not contribute either directly or indirectly to a constraint, or a function return value, typically indicates a mistake in the implementation of the circuit. For example, consider the following `BinSum` template from circomlib where we've changed the final constraint to introduce a bug.\n\n```cpp\n  template BinSum(n, ops) {\n      var nout = nbits((2 ** n - 1) * ops);\n      var lin = 0;\n      var lout = 0;\n\n      signal input in[ops][n];\n      signal output out[nout];\n\n      var e2 = 1;\n      for (var k = 0; k < n; k++) {\n          for (var j = 0; j < ops; j++) {\n              lin += in[j][k] * e2;\n          }\n          e2 = e2 + e2;\n      }\n\n      e2 = 1;\n      for (var k = 0; k < nout; k++) {\n          out[k] <-- (lin >> k) & 1;\n          out[k] * (out[k] - 1) === 0;\n\n          lout += out[k] * e2;  // The value assigned here is not used.\n          e2 = e2 + e2;\n      }\n\n      lin === nout;  // Should use `lout`, but uses `nout` by mistake.\n  }\n```\n\nHere, `lout` no longer influences the generated circuit, which is detected by Circomspect.\n\n### Shadowing variable\n\nA shadowing variable declaration is a declaration of a variable with the same name as a previously declared variable. This does not have to be a problem, but if a variable declared in an outer scope is shadowed by mistake, this could change the semantics of the program which would be an issue.\n\nFor example, consider this function which is supposed to compute the number of bits needed to represent `a`.\n\n```cpp\n  function numberOfBits(a) {\n      var n = 1;\n      var r = 0;  // Shadowed variable is declared here.\n      while (n - 1 < a) {\n          var r = r + 1;  // Shadowing declaration here.\n          n *= 2;\n      }\n      return r;\n  }\n```\n\nSince a new variable `r` is declared in the while-statement body, the outer variable is never updated and the return value is always 0.\n\n### Signal assignment\n\nSignals should typically be assigned using the constraint assignment operator `<==`. This ensures that the circuit and witness generation stay in sync. If `<--` is used it is up to the developer to ensure that the signal is properly constrained. Circomspect will try to detect if the right-hand side of the assignment is a quadratic expression. If it is, the signal assignment can be rewritten using the constraint assignment operator `<==`.\n\nHowever, sometimes it is not possible to express the assignment using a quadratic expression. In this case Circomspect will try to list all constraints containing the assigned signal to make it easier for the developer (or reviewer) to ensure that the variable is properly constrained.\n\nThe Tornado Cash codebase was originally affected by an issue of this type. For details see [the Tornado Cash disclosure](https://tornado-cash.medium.com/tornado-cash-got-hacked-by-us-b1e012a3c9a8).\n\n### Under-constrained signal\n\nUnder-constrained signals are one of the most common issues in zero-knowledge circuits. Circomspect will flag intermediate signals that only occur in a single constraint. Since intermediate signals are not available outside the template, this typically indicates an issue with the implementation.\n\n### Unused output signal\n\nWhen a template is instantiated, the corresponding input signals must be constrained. This is typically also true for the output signals defined by the template, but if we fail to constrain an output signal defined by a template this will not be flagged as an error by the compiler. There are examples (like `Num2Bits` from Circomlib) where the template constrains the input and no further constraints on the output are required. However, in the general case, failing to constrain the output from a template indicates a potential mistake that should be investigated.\n\nCircomspect will generate a warning whenever it identifies an instantiated template where one or more output signals defined by the template are not constrained. Each location can then be manually reviewed for correctness.\n\nThis type of issue [was identified by Veridise](https://medium.com/veridise/circom-pairing-a-million-dollar-zk-bug-caught-early-c5624b278f25) during a review of the circom-pairing library.\n\n### Constant branching condition\n\nIf a branching statement condition always evaluates to either `true` or `false`, this means that the branch is either always taken, or never taken. This typically indicates a mistake in the code which should be fixed.\n\n### Non-strict binary conversion\n\nUsing `Num2Bits` and `Bits2Num` from\n[Circomlib](https://github.com/iden3/circomlib) to convert a field element to\nand from binary form is only safe if the input size is smaller than the size of\nthe prime. If not, there may be multiple correct representations of the input\nwhich could cause issues, since we typically expect the circuit output to be\nuniquely determined by the input.\n\nFor example, suppose that we create a component `n2b` given by `Num2Bits(254)` and set the input to `1`. Now, both the binary representation of `1` _and_ the representation of `p + 1` (where `p` is the order of the underlying finite field) will satisfy the circuit over BN254, since both are 254-bit numbers. If you cannot restrict the input size below the prime size you should use the strict versions `Num2Bits_strict` and `Bits2Num_strict` to convert to and from binary representation. Circomspect will generate a warning if it cannot prove (using constant propagation) that the input size passed to `Num2Bits` or `Bits2Num` is less than the size of the prime in bits.\n\n### Unconstrained less-than\n\nThe Circomlib `LessThan` template takes an input size as argument. If the individual input signals are not constrained to be non-negative (for example using the Circomlib `Num2Bits` circuit), it is possible to find inputs `a` and `b` such that `a > b`, but `LessThan` still evaluates to true when given `a` and `b` as inputs.\n\nFor example, consider the following template which takes a single input signal\nand attempts to constrain it to be less than two.\n\n```cpp\n  template LessThanTwo() {\n    signal input in;\n\n    component lt = LessThan(8);\n    lt.in[0] <== in;\n    lt.in[1] <== 2;\n\n    lt.out === 1;\n  }\n```\n\nSuppose that we define the private input `in` as `p - 254`, where `p` is the prime order of the field. Clearly, `p - 254` is not less than two (at least not when viewed as an unsigned integer), so we would perhaps expect `LessThanTwo` to fail. However, looking at [the implementation](https://github.com/iden3/circomlib/blob/cff5ab6288b55ef23602221694a6a38a0239dcc0/circuits/comparators.circom#L89-L99) of `LessThan`, we see that `lt.out` is given by\n\n```cpp\n    1 - n2b.out[8] = 1 - bit 8 of (p - 254 + (1 << 8) - 2) = 1 - 0 = 1.\n```\n\nIt follows that `p - 254` satisfies `LessThanTwo()`, which is probably not what we expected. Note that, `p - 254` is equal to -254 which _is_ less than two, so there is nothing wrong with the Circomlib `LessThan` circuit. This may just be unexpected behavior if we're thinking of field elements as unsigned integers.\n\nCircomspect will check if the inputs to `LessThan` are constrained to be strictly less than `log(p) - 1` bits using `Num2Bits`. This guarantees that both inputs are non-negative, which avoids this issue. If it cannot prove that both inputs are constrained in this way, a warning is generated.\n\n### Unconstrained division\n\nSince division cannot be expressed directly using a quadratic constraint, it is common to use the following pattern to ensure that the signal `c` is equal to `a / b`.\n\n```cpp\n    c <-- a / b;\n    c * b === a;\n```\n\nThis forces `c` to be equal to `a / b` during witness generation, and checks that `c * b = a` during proof verification. However, the statement `c = a / b` only makes sense when `b` is non-zero, whereas `c * b = a` may be true even when `b` is zero. For this reason it is important to also constrain the divisor `b` to ensure that it is non-zero when the proof is verified.\n\nCircomspect will identify signal assignments on the form `c <-- a / b` and ensure that the expression `b` is constrained to be non-zero using the Circomlib `IsZero` template. If no such constraint is found, a warning is emitted.\n\n### BN254 specific circuit\n\nCircom defaults to using the BN254 scalar field (a 254-bit prime field),\nbut it also supports BSL12-381 (which has a 255-bit scalar field) and\nGoldilocks (with a 64-bit scalar field). However, since there are no constants denoting either the prime or the prime size in bits available in the Circom language, some Circomlib templates like `Sign` (which returns the sign of the input signal), and `AliasCheck` (used by the strict versions of `Num2Bits` and `Bits2Num`), hardcode either the BN254 prime size or some other constant related to BN254. Using these circuits with a custom prime may thus lead to unexpected results and should be avoided.\n\nCircomlib templates that may be problematic when used together with curves other than BN254 include the following circuit definitions. (An `x` means that the template should not be used together with the corresponding curve.)\n\n| Template                  | Goldilocks (64 bits) | BLS12-381 (255 bits) |\n| :------------------------ | :------------------: | :------------------: |\n| `AliasCheck`              |           x          |           x          |\n| `BabyPbk`                 |           x          |                      |\n| `Bits2Num_strict`         |           x          |           x          |\n| `Num2Bits_strict`         |           x          |           x          |\n| `CompConstant`            |           x          |           x          |\n| `EdDSAVerifier`           |           x          |           x          |\n| `EdDSAMiMCVerifier`       |           x          |           x          |\n| `EdDSAMiMCSpongeVerifier` |           x          |           x          |\n| `EdDSAPoseidonVerifier`   |           x          |           x          |\n| `EscalarMulAny`           |           x          |                      |\n| `MiMC7`                   |           x          |                      |\n| `MultiMiMC7`              |           x          |                      |\n| `MiMCFeistel`             |           x          |                      |\n| `MiMCSponge`              |           x          |                      |\n| `Pedersen`                |           x          |                      |\n| `Bits2Point_strict`       |           x          |           x          |\n| `Point2Bits_strict`       |           x          |           x          |\n| `PoseidonEx`              |           x          |                      |\n| `Poseidon`                |           x          |                      |\n| `Sign`                    |           x          |           x          |\n| `SMTHash1`                |           x          |                      |\n| `SMTHash2`                |           x          |                      |\n| `SMTProcessor`            |           x          |           x          |\n| `SMTProcessorLevel`       |           x          |                      |\n| `SMTVerifier`             |           x          |           x          |\n| `SMTVerifierLevel`        |           x          |                      |\n\n### Overly complex function or template\n\nAs functions and templates grow in complexity they become more difficult to review and maintain. This typically indicates that the code should be refactored into smaller, more easily understandable, components. Circomspect uses cyclomatic complexity to estimate the complexity of each function and template, and will generate a warning if the code is considered too complex. Circomspect will also generate a warning if a function or template takes too many arguments, as this also impacts the readability of the code.\n\n### Bitwise complement\n\nCircom supports taking the 256-bit complement `~x` of a field element `x`. Since the result is reduced modulo `p`, it will typically not satisfy the expected relations `(~x)ᵢ == ~(xᵢ)` for each bit `i`, which could lead to surprising results.\n\n### Field element arithmetic\n\nCircom supports a large number of arithmetic expressions. Since arithmetic expressions can overflow or underflow in Circom it is worth paying extra attention to field arithmetic to ensure that elements are constrained to the correct range.\n\n### Field element comparison\n\nField elements are normalized to the interval `(-p/2, p/2]` before they are compared, by first reducing them modulo `p` and then mapping them to the correct interval by subtracting `p` from the value `x`, if `x` is greater than `p/2`. In particular, this means that `p/2 + 1 < 0 < p/2 - 1`. This can be surprising if you are used to thinking of elements in `GF(p)` as unsigned integers.\n"
  },
  {
    "path": "doc/demo.cast",
    "content": "{\"version\": 2, \"width\": 114, \"height\": 35, \"timestamp\": 1656748733, \"env\": {\"SHELL\": \"/bin/zsh\", \"TERM\": \"xterm-256color\"}}\n[0.048458, \"o\", \"Restored session: Sat Jul  2 09:56:58 CEST 2022\\r\\n\"]\n[0.25259, \"o\", \"\\u001b[1m\\u001b[7m%\\u001b[27m\\u001b[1m\\u001b[0m                                                                                                                 \\r \\r\"]\n[0.252916, \"o\", \"\\u001b]7;file://DELLINSON153567/Users/fredah/Projects/circomspect\\u0007\"]\n[0.40162, \"o\", \"\\r\\u001b[0m\\u001b[27m\\u001b[24m\\u001b[J\\r\\n\\u001b[1;36mcircomspect\\u001b[0m on \\u001b[1;35m🌱 \\u001b[0m\\u001b[1;35mmain\\u001b[0m via \\u001b[1;31m🦀 \\u001b[0m\\u001b[1;31mv1.61.0\\u001b[0m \\r\\n\\u001b[1;32m❯\\u001b[0m \\u001b[K\\u001b[?2004h\"]\n[1.660509, \"o\", \"c\"]\n[1.662497, \"o\", \"\\bc\\u001b[90mircomspect --output-level INFO examples/unconstrained-signal.circom\\u001b[39m\\u001b[67D\"]\n[1.906025, \"o\", \"\\bc\\u001b[39mi\"]\n[2.008057, \"o\", \"\\u001b[39mr\"]\n[2.205441, \"o\", \"\\u001b[39mc\"]\n[2.325101, \"o\", \"\\u001b[39mo\"]\n[2.381572, \"o\", \"\\u001b[39mm\"]\n[2.546839, \"o\", \"\\u001b[39ms\"]\n[2.655813, \"o\", \"\\u001b[39mp\"]\n[2.803134, \"o\", \"\\u001b[39me\"]\n[2.970875, \"o\", \"\\u001b[39mc\"]\n[3.193901, \"o\", \"\\u001b[39mt\"]\n[3.282417, \"o\", \"\\u001b[39m \"]\n[3.811358, \"o\", \"\\u001b[39m-\"]\n[3.944232, \"o\", \"\\u001b[39m-\"]\n[4.172232, \"o\", \"\\u001b[39mh\\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[39m \\u001b[53D\"]\n[4.173668, \"o\", \"\\u001b[90melp\\u001b[39m\\b\\b\\b\"]\n[4.229432, \"o\", \"\\u001b[39me\"]\n[4.335885, \"o\", \"\\u001b[39ml\"]\n[4.395073, \"o\", \"\\u001b[39mp\"]\n[4.738144, \"o\", \"\\u001b[?2004l\\r\\r\\n\"]\n[4.759646, \"o\", \"circomspect 0.2.1\\r\\nA static analyzer for Circom programs\\r\\n\\r\\nUSAGE:\\r\\n    circomspect [OPTIONS] <input>\\r\\n\\r\\nFLAGS:\\r\\n    -h, --help       Prints help information\\r\\n    -V, --version    Prints version information\\r\\n\\r\\nOPTIONS:\\r\\n        --output-level <level>          Output level (either INFO, WARNING, or ERROR) [default: WARNING]\\r\\n        --sarif-file <output>           Output analysis results to a Sarif file\\r\\n        --compiler-version <version>    Expected compiler version [default: 2.0.3]\\r\\n\\r\\nARGS:\\r\\n    <input>    Initial input file\\r\\n\"]\n[4.760177, \"o\", \"\\u001b[1m\\u001b[7m%\\u001b[27m\\u001b[1m\\u001b[0m                                                                                                                 \\r \\r\"]\n[4.760835, \"o\", \"\\u001b]7;file://DELLINSON153567/Users/fredah/Projects/circomspect\\u0007\"]\n[4.907543, \"o\", \"\\r\\u001b[0m\\u001b[27m\\u001b[24m\\u001b[J\\r\\n\\u001b[1;36mcircomspect\\u001b[0m on \\u001b[1;35m🌱 \\u001b[0m\\u001b[1;35mmain\\u001b[0m via \\u001b[1;31m🦀 \\u001b[0m\\u001b[1;31mv1.61.0\\u001b[0m \\r\\n\\u001b[1;32m❯\\u001b[0m \\u001b[K\\u001b[?2004h\"]\n[12.362466, \"o\", \"c\"]\n[12.364043, \"o\", \"\\bc\\u001b[90mircomspect --help\\u001b[39m\\u001b[17D\"]\n[12.511489, \"o\", \"\\bc\\u001b[39mi\"]\n[12.659933, \"o\", \"\\u001b[39mr\"]\n[12.87152, \"o\", \"\\u001b[39mc\"]\n[12.990419, \"o\", \"\\u001b[39mo\"]\n[13.03172, \"o\", \"\\u001b[39mm\"]\n[13.214778, \"o\", \"\\u001b[39ms\"]\n[13.306093, \"o\", \"\\u001b[39mp\"]\n[13.486231, \"o\", \"\\u001b[39me\"]\n[13.651574, \"o\", \"\\u001b[39mc\"]\n[13.905489, \"o\", \"\\u001b[39mt\"]\n[14.025442, \"o\", \"\\u001b[39m \"]\n[14.548997, \"o\", \"\\u001b[39m-\"]\n[14.701112, \"o\", \"\\u001b[39m-\"]\n[15.289916, \"o\", \"\\u001b[39mo\\u001b[39m \\u001b[39m \\u001b[39m \\b\\b\\b\"]\n[15.291958, \"o\", \"\\u001b[90mutput-level INFO examples/unconstrained-signal.circom\\u001b[39m\\u001b[53D\"]\n[15.391246, \"o\", \"\\u001b[39mu\"]\n[15.480107, \"o\", \"\\u001b[39mt\"]\n[16.032857, \"o\", \"\\u001b[39mp\\u001b[39mu\\u001b[39mt\\u001b[39m-\\u001b[39ml\\u001b[39me\\u001b[39mv\\u001b[39me\\u001b[39ml\\u001b[39m \\u001b[39mI\\u001b[39mN\\u001b[39mF\\u001b[39mO\\u001b[39m \\u001b[39me\\u001b[39mx\\u001b[39ma\\u001b[39mm\\u001b[39mp\\u001b[39ml\\u001b[39me\\u001b[39ms\\u001b[39m/\\u001b[39mu\\u001b[39mn\\u001b[39mc\\u001b[39mo\\u001b[39mn\\u001b[39ms\\u001b[39mt\\u001b[39mr\\u001b[39ma\\u001b[39mi\\u001b[39mn\\u001b[39me\\u001b[39md\\u001b[39m-\\u001b[39ms\\u001b[39mi\\u001b[39mg\\u001b[39mn\\u001b[39ma\\u001b[39ml\\u001b[39m.\\u001b[39mc\\u001b[39mi\\u001b[39mr\\u001b[39mc\\u001b[39mo\\u001b[39mm\"]\n[16.51743, \"o\", \"\\u001b[?2004l\\r\\r\\n\"]\n[16.542809, \"o\", \"\\u001b[0m\\u001b[1m\\u001b[33mwarning\\u001b[0m\\u001b[1m: The variable `value` is assigned a value, but this value is never read.\\u001b[0m\\r\\n   \\u001b[0m\\u001b[34m┌─\\u001b[0m \\\"examples/unconstrained-signal.circom\\\":13:5\\r\\n   \\u001b[0m\\u001b[34m│\\u001b[0m\\r\\n\\u001b[0m\\u001b[34m13\\u001b[0m \\u001b[0m\\u001b[34m│\\u001b[0m     \\u001b[0m\\u001b[33mvar value = 0\\u001b[0m;\\r\\n   \\u001b[0m\\u001b[34m│\\u001b[0m     \\u001b[0m\\u001b[33m^^^^^^^^^^^^^\\u001b[0m \\u001b[0m\\u001b[33mThe value assigned here is never read.\\u001b[0m\\r\\n\\r\\n\\u001b[0m\\u001b[1m\\u001b[33mwarning\\u001b[0m\\u001b[1m: Using the signal assignment operator `<--` does not constrain the assigned signal.\\u001b[0m\\r\\n   \\u001b[0m\\u001b[34m┌─\\u001b[0m \\\"examples/unconstrained-signal.circom\\\":17:9\\r\\n   \\u001b[0m\\u001b[34m│\\u001b[0m\\r\\n\\u001b[0m\\u001b[34m17\\u001b[0m \\u001b[0m\\u001b[34m│\\u001b[0m         \\u001b[0m\\u001b[33mout[i] <-- (in >> i) & 1\\u001b[0m;\\r\\n   \\u001b[0m\\u001b[34m│\\u001b[0m         \\u001b[0m\\u001b[33m^^^^^^^^^^^^^^^^^^^^^^^^\\u001b[0m \\u001b[0m\\u001b[33mThe assigned signal `out` is not constrained here.\\u001b[0m\\r\\n   \\u001b[0m\\u001b[34m│\\u001b[0m\\r\\n   \\u001b[0m\\u001b[34m=\\u001b[0m Consider using the constraint assignment operator `<==` instead.\\r\\n\\r\\n\\u001b[0m\\u001b[1m\\u001b[38;5;10mnote\\u001b[0m\\u001b[1m: Field element arithmetic could overflow, which may produce unexpected results.\\u001b[0m\\r\\n   \\u001b[0m\\u001b[34m\"]\n[16.542959, \"o\", \"┌─\\u001b[0m \\\"examples/unconstrained-signal.circom\\\":19:9\\r\\n   \\u001b[0m\\u001b[34m│\\u001b[0m\\r\\n\\u001b[0m\\u001b[34m19\\u001b[0m \\u001b[0m\\u001b[34m│\\u001b[0m         \\u001b[0m\\u001b[32mresult += out[i] * power\\u001b[0m;\\r\\n   \\u001b[0m\\u001b[34m│\\u001b[0m         \\u001b[0m\\u001b[32m^^^^^^^^^^^^^^^^^^^^^^^^\\u001b[0m \\u001b[0m\\u001b[32mField element arithmetic here.\\u001b[0m\\r\\n\\r\\n\\u001b[0m\\u001b[1m\\u001b[38;5;10mnote\\u001b[0m\\u001b[1m: Field element arithmetic could overflow, which may produce unexpected results.\\u001b[0m\\r\\n   \\u001b[0m\\u001b[34m┌─\\u001b[0m \\\"examples/unconstrained-signal.circom\\\":20:17\\r\\n   \\u001b[0m\\u001b[34m│\\u001b[0m\\r\\n\\u001b[0m\\u001b[34m20\\u001b[0m \\u001b[0m\\u001b[34m│\\u001b[0m         power = \\u001b[0m\\u001b[32mpower + power\\u001b[0m;\\r\\n   \\u001b[0m\\u001b[34m│\\u001b[0m                 \\u001b[0m\\u001b[32m^^^^^^^^^^^^^\\u001b[0m \\u001b[0m\\u001b[32mField element arithmetic here.\\u001b[0m\\r\\n\\r\\n\\u001b[0m\\u001b[1m\\u001b[38;5;10mnote\\u001b[0m\\u001b[1m: Field element arithmetic could overflow, which may produce unexpected results.\\u001b[0m\\r\\n   \\u001b[0m\\u001b[34m┌─\\u001b[0m \\\"examples/unconstrained-signal.circom\\\":16:28\\r\\n   \\u001b[0m\\u001b[34m│\\u001b[0m\\r\\n\\u001b[0m\\u001b[34m16\\u001b[0m \\u001b[0m\\u001b[34m│\\u001b[0m     for (var i = 0; i < n; \\u001b[0m\\u001b[32mi++\\u001b[0m) {\\r\\n   \\u001b[0m\\u001b[34m│\\u001b[0m                            \\u001b[0m\\u001b[32m^^^\\u001b[0m \\u001b[0m\\u001b[32mF\"]\n[16.543137, \"o\", \"ield element arithmetic here.\\u001b[0m\\r\\n\\r\\n\\u001b[0m\\u001b[1m\\u001b[38;5;10mnote\\u001b[0m\\u001b[1m: Comparisons with field elements greater than `p/2` may produce unexpected results.\\u001b[0m\\r\\n   \\u001b[0m\\u001b[34m┌─\\u001b[0m \\\"examples/unconstrained-signal.circom\\\":16:21\\r\\n   \\u001b[0m\\u001b[34m│\\u001b[0m\\r\\n\\u001b[0m\\u001b[34m16\\u001b[0m \\u001b[0m\\u001b[34m│\\u001b[0m     for (var i = 0; \\u001b[0m\\u001b[32mi < n\\u001b[0m; i++) {\\r\\n   \\u001b[0m\\u001b[34m│\\u001b[0m                     \\u001b[0m\\u001b[32m^^^^^\\u001b[0m \\u001b[0m\\u001b[32mField element comparison here.\\u001b[0m\\r\\n   \\u001b[0m\\u001b[34m│\\u001b[0m\\r\\n   \\u001b[0m\\u001b[34m=\\u001b[0m Field elements are always normalized to the interval `(p/2, p/2]` before they are compared.\\r\\n\\r\\n\"]\n[16.543258, \"o\", \"\\u001b[0m\\u001b[1m\\u001b[33mwarning\\u001b[0m\\u001b[1m: Using the signal assignment operator `<--` does not constrain the assigned signal.\\u001b[0m\\r\\n  \\u001b[0m\\u001b[34m┌─\\u001b[0m \\\"examples/unconstrained-signal.circom\\\":6:3\\r\\n  \\u001b[0m\\u001b[34m│\\u001b[0m\\r\\n\\u001b[0m\\u001b[34m6\\u001b[0m \\u001b[0m\\u001b[34m│\\u001b[0m   in + 2 === out;\\r\\n  \\u001b[0m\\u001b[34m│\\u001b[0m   \\u001b[0m\\u001b[34m---------------\\u001b[0m \\u001b[0m\\u001b[34mThe signal `out` is constrained here.\\u001b[0m\\r\\n\\u001b[0m\\u001b[34m7\\u001b[0m \\u001b[0m\\u001b[34m│\\u001b[0m   \\u001b[0m\\u001b[33mout <-- in + 1\\u001b[0m;\\r\\n  \\u001b[0m\\u001b[34m│\\u001b[0m   \\u001b[0m\\u001b[33m^^^^^^^^^^^^^^\\u001b[0m \\u001b[0m\\u001b[33mThe assigned signal `out` is not constrained here.\\u001b[0m\\r\\n\\r\\n\\u001b[0m\\u001b[1m\\u001b[38;5;10mnote\\u001b[0m\\u001b[1m: Field element arithmetic could overflow, which may produce unexpected results.\\u001b[0m\\r\\n  \\u001b[0m\\u001b[34m┌─\\u001b[0m \\\"examples/unconstrained-signal.circom\\\":6:3\\r\\n  \\u001b[0m\\u001b[34m│\\u001b[0m\\r\\n\\u001b[0m\\u001b[34m6\\u001b[0m \\u001b[0m\\u001b[34m│\\u001b[0m   \\u001b[0m\\u001b[32min + 2\\u001b[0m === out;\\r\\n  \\u001b[0m\\u001b[34m│\\u001b[0m   \\u001b[0m\\u001b[32m^^^^^^\\u001b[0m \\u001b[0m\\u001b[32mField element arithmetic here.\\u001b[0m\\r\\n\\r\\n\\u001b[0m\\u001b[1m\\u001b[38;5;10mnote\\u001b[0m\\u001b[1m: Field element arithmetic could overflow, which may produce unexpected results.\\u001b[0m\\r\\n  \\u001b[0m\\u001b[34m┌─\"]\n[16.543378, \"o\", \"\\u001b[0m \\\"examples/unconstrained-signal.circom\\\":7:11\\r\\n  \\u001b[0m\\u001b[34m│\\u001b[0m\\r\\n\\u001b[0m\\u001b[34m7\\u001b[0m \\u001b[0m\\u001b[34m│\\u001b[0m   out <-- \\u001b[0m\\u001b[32min + 1\\u001b[0m;\\r\\n  \\u001b[0m\\u001b[34m│\\u001b[0m           \\u001b[0m\\u001b[32m^^^^^^\\u001b[0m \\u001b[0m\\u001b[32mField element arithmetic here.\\u001b[0m\\r\\n\\r\\n\"]\n[16.54405, \"o\", \"\\u001b[1m\\u001b[7m%\\u001b[27m\\u001b[1m\\u001b[0m                                                                                                                 \\r \\r\"]\n[16.54466, \"o\", \"\\u001b]7;file://DELLINSON153567/Users/fredah/Projects/circomspect\\u0007\"]\n[16.686447, \"o\", \"\\r\\u001b[0m\\u001b[27m\\u001b[24m\\u001b[J\\r\\n\\u001b[1;36mcircomspect\\u001b[0m on \\u001b[1;35m🌱 \\u001b[0m\\u001b[1;35mmain\\u001b[0m via \\u001b[1;31m🦀 \\u001b[0m\\u001b[1;31mv1.61.0\\u001b[0m \\r\\n\\u001b[1;32m❯\\u001b[0m \\u001b[K\\u001b[?2004h\"]\n[28.919242, \"o\", \"\\u001b[?2004l\\r\\r\\n\"]\n[28.922062, \"o\", \"\\r\\nSaving session...\"]\n[28.939634, \"o\", \"\\r\\n...saving history...\"]\n[28.956811, \"o\", \"truncating history files...\"]\n[28.970221, \"o\", \"\\r\\n...\"]\n[28.970294, \"o\", \"completed.\\r\\n\"]\n"
  },
  {
    "path": "parser/Cargo.toml",
    "content": "[package]\nname = \"circomspect-parser\"\nversion = \"2.2.0\"\nedition = \"2021\"\nrust-version = \"1.65\"\nbuild = \"build.rs\"\nlicense = \"LGPL-3.0-only\"\ndescription = \"Support crate for the Circomspect static analyzer\"\nrepository = \"https://github.com/trailofbits/circomspect\"\nauthors = [\n  \"Hermenegildo <hermegar@ucm.es>\",\n  \"Fredrik Dahlgren <fredrik.dahlgren@trailofbits.com>\",\n]\n\n[build-dependencies]\nrustc-hex = \"2.0\"\nlalrpop = { version = \"0.20\", features = [\"lexer\"] }\nnum-bigint-dig = \"0.8\"\nnum-traits = \"0.2\"\n\n[dependencies]\nprogram_structure = { package = \"circomspect-program-structure\", version = \"2.1.4\", path = \"../program_structure\" }\nlalrpop = { version = \"0.20\", features = [\"lexer\"] }\nlalrpop-util = \"0.20\"\nlog = \"0.4\"\nregex = \"1.7\"\nrustc-hex = \"2.1\"\nnum-bigint-dig = \"0.8\"\nnum-traits = \"0.2\"\nserde = \"1.0\"\nserde_derive = \"1.0\"\n\n[dev-dependencies]\nprogram_structure = { package = \"circomspect-program-structure\", version = \"2.1.4\", path = \"../program_structure\" }\n"
  },
  {
    "path": "parser/build.rs",
    "content": "extern crate lalrpop;\n\nfn main() {\n    lalrpop::process_root().unwrap();\n}\n"
  },
  {
    "path": "parser/src/errors.rs",
    "content": "use program_structure::ast::{Meta, Version};\nuse program_structure::report_code::ReportCode;\nuse program_structure::report::Report;\nuse program_structure::file_definition::{FileID, FileLocation};\n\npub struct UnclosedCommentError {\n    pub location: FileLocation,\n    pub file_id: FileID,\n}\n\nimpl UnclosedCommentError {\n    pub fn into_report(self) -> Report {\n        let mut report = Report::error(\"Unterminated comment.\".to_string(), ReportCode::ParseFail);\n        report.add_primary(self.location, self.file_id, \"Comment starts here.\".to_string());\n        report\n    }\n}\n\npub struct ParsingError {\n    pub location: FileLocation,\n    pub file_id: FileID,\n    pub message: String,\n}\n\nimpl ParsingError {\n    pub fn into_report(self) -> Report {\n        let mut report = Report::error(self.message, ReportCode::ParseFail);\n        report.add_primary(\n            self.location,\n            self.file_id,\n            \"This token is invalid or unexpected here.\".to_string(),\n        );\n        report\n    }\n}\n\npub struct FileOsError {\n    pub path: String,\n}\nimpl FileOsError {\n    pub fn into_report(self) -> Report {\n        Report::error(format!(\"Failed to open file `{}`.\", self.path), ReportCode::ParseFail)\n    }\n}\n\npub struct IncludeError {\n    pub path: String,\n    pub file_id: Option<FileID>,\n    pub file_location: FileLocation,\n}\nimpl IncludeError {\n    pub fn into_report(self) -> Report {\n        let mut report =\n            Report::error(format!(\"Failed to open file `{}`.\", self.path), ReportCode::ParseFail);\n        if let Some(file_id) = self.file_id {\n            report.add_primary(self.file_location, file_id, \"File included here.\".to_string());\n        }\n        report\n    }\n}\n\npub struct MultipleMainError;\nimpl MultipleMainError {\n    pub fn produce_report() -> Report {\n        Report::error(\n            \"Multiple main components found in the project structure.\".to_string(),\n            ReportCode::MultipleMainInComponent,\n        )\n    }\n}\n\npub struct CompilerVersionError {\n    pub path: String,\n    pub required_version: Version,\n    pub version: Version,\n}\nimpl CompilerVersionError {\n    pub fn into_report(self) -> Report {\n        let message = format!(\n            \"The file `{}` requires version {}, which is not supported by Circomspect (version {}).\",\n            self.path,\n            version_string(&self.required_version),\n            version_string(&self.version),\n        );\n        Report::error(message, ReportCode::CompilerVersionError)\n    }\n}\n\npub struct NoCompilerVersionWarning {\n    pub path: String,\n    pub version: Version,\n}\nimpl NoCompilerVersionWarning {\n    pub fn produce_report(error: Self) -> Report {\n        Report::warning(\n            format!(\n                \"The file `{}` does not include a version pragma. Assuming version {}.\",\n                error.path,\n                version_string(&error.version)\n            ),\n            ReportCode::NoCompilerVersionWarning,\n        )\n    }\n}\n\npub struct AnonymousComponentError {\n    pub meta: Option<Meta>,\n    pub message: String,\n    pub primary: Option<String>,\n}\n\nimpl AnonymousComponentError {\n    pub fn new(meta: Option<&Meta>, message: &str, primary: Option<&str>) -> Self {\n        AnonymousComponentError {\n            meta: meta.cloned(),\n            message: message.to_string(),\n            primary: primary.map(ToString::to_string),\n        }\n    }\n\n    pub fn into_report(self) -> Report {\n        let mut report = Report::error(self.message, ReportCode::AnonymousComponentError);\n        if let Some(meta) = self.meta {\n            let primary = self.primary.unwrap_or_else(|| \"The problem occurs here.\".to_string());\n            report.add_primary(meta.file_location(), meta.get_file_id(), primary);\n        }\n        report\n    }\n\n    pub fn boxed_report(meta: &Meta, message: &str) -> Box<Report> {\n        Box::new(Self::new(Some(meta), message, None).into_report())\n    }\n}\n\npub struct TupleError {\n    pub meta: Option<Meta>,\n    pub message: String,\n    pub primary: Option<String>,\n}\n\nimpl TupleError {\n    pub fn new(meta: Option<&Meta>, message: &str, primary: Option<&str>) -> Self {\n        TupleError {\n            meta: meta.cloned(),\n            message: message.to_string(),\n            primary: primary.map(ToString::to_string),\n        }\n    }\n\n    pub fn into_report(self) -> Report {\n        let mut report = Report::error(self.message, ReportCode::TupleError);\n        if let Some(meta) = self.meta {\n            let primary = self.primary.unwrap_or_else(|| \"The problem occurs here.\".to_string());\n            report.add_primary(meta.file_location(), meta.get_file_id(), primary);\n        }\n        report\n    }\n\n    pub fn boxed_report(meta: &Meta, message: &str) -> Box<Report> {\n        Box::new(Self::new(Some(meta), message, None).into_report())\n    }\n}\n\nfn version_string(version: &Version) -> String {\n    format!(\"{}.{}.{}\", version.0, version.1, version.2)\n}\n"
  },
  {
    "path": "parser/src/include_logic.rs",
    "content": "use crate::errors::FileOsError;\n\nuse log::debug;\n\nuse super::errors::IncludeError;\nuse program_structure::ast::Include;\nuse program_structure::report::{Report, ReportCollection};\nuse std::collections::HashSet;\nuse std::ffi::OsString;\nuse std::fs;\nuse std::path::PathBuf;\n\npub struct FileStack {\n    current_location: Option<PathBuf>,\n    black_paths: HashSet<PathBuf>,\n    user_inputs: HashSet<PathBuf>,\n    libraries: Vec<Library>,\n    stack: Vec<PathBuf>,\n}\n\n#[derive(Debug)]\nstruct Library {\n    dir: bool,\n    path: PathBuf,\n}\n\nimpl FileStack {\n    pub fn new(paths: &[PathBuf], libs: &[PathBuf], reports: &mut ReportCollection) -> FileStack {\n        let mut result = FileStack {\n            current_location: None,\n            black_paths: HashSet::new(),\n            user_inputs: HashSet::new(),\n            libraries: Vec::new(),\n            stack: Vec::new(),\n        };\n        result.add_libraries(libs, reports);\n        result.add_files(paths, reports);\n        result.user_inputs = result.stack.iter().cloned().collect::<HashSet<_>>();\n\n        result\n    }\n\n    fn add_libraries(&mut self, libs: &[PathBuf], reports: &mut ReportCollection) {\n        for path in libs {\n            if path.is_dir() {\n                self.libraries.push(Library { dir: true, path: path.clone() });\n            } else if let Some(extension) = path.extension() {\n                // Add Circom files to file stack.\n                if extension == \"circom\" {\n                    match fs::canonicalize(path) {\n                        Ok(path) => self.libraries.push(Library { dir: false, path: path.clone() }),\n                        Err(_) => {\n                            reports.push(\n                                FileOsError { path: path.display().to_string() }.into_report(),\n                            );\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    fn add_files(&mut self, paths: &[PathBuf], reports: &mut ReportCollection) {\n        for path in paths {\n            if path.is_dir() {\n                // Handle directories on a best effort basis only.\n                if let Ok(entries) = fs::read_dir(path) {\n                    let paths: Vec<_> = entries.flatten().map(|x| x.path()).collect();\n                    self.add_files(&paths, reports);\n                }\n            } else if let Some(extension) = path.extension() {\n                // Add Circom files to file stack.\n                if extension == \"circom\" {\n                    match fs::canonicalize(path) {\n                        Ok(path) => self.stack.push(path),\n                        Err(_) => {\n                            reports.push(\n                                FileOsError { path: path.display().to_string() }.into_report(),\n                            );\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    pub fn add_include(&mut self, include: &Include) -> Result<(), Box<Report>> {\n        let mut location = self.current_location.clone().expect(\"parsing file\");\n        location.push(include.path.clone());\n        match fs::canonicalize(&location) {\n            Ok(path) => {\n                if !self.black_paths.contains(&path) {\n                    debug!(\"adding local or absolute include `{}`\", location.display());\n                    self.stack.push(path);\n                }\n                Ok(())\n            }\n            Err(_) => self.include_library(include),\n        }\n    }\n\n    fn include_library(&mut self, include: &Include) -> Result<(), Box<Report>> {\n        // try and perform library resolution on the include\n        // at this point any absolute path has been handled by the push in add_include\n        let pathos = OsString::from(include.path.clone());\n        for lib in &self.libraries {\n            if lib.dir {\n                // only match relative paths that do not start with .\n                if include.path.find('.') == Some(0) {\n                    continue;\n                }\n\n                let libpath = lib.path.join(&include.path);\n                debug!(\"searching for `{}` in `{}`\", include.path, lib.path.display());\n                if fs::canonicalize(&libpath).is_ok() {\n                    debug!(\"adding include `{}` from directory\", libpath.display());\n                    self.stack.push(libpath);\n                    return Ok(());\n                }\n            } else {\n                // only match include paths with a single component i.e. lib.circom and not dir/lib.circom or\n                // ./lib.circom\n                if include.path.find(std::path::MAIN_SEPARATOR).is_none() {\n                    debug!(\"checking if `{}` matches `{}`\", include.path, lib.path.display());\n                    if lib.path.file_name().expect(\"good library file\") == pathos {\n                        debug!(\"adding include `{}` from file\", lib.path.display());\n                        self.stack.push(lib.path.clone());\n                        return Ok(());\n                    }\n                }\n            }\n        }\n\n        let error = IncludeError {\n            path: include.path.clone(),\n            file_id: include.meta.file_id,\n            file_location: include.meta.file_location(),\n        };\n        Err(Box::new(error.into_report()))\n    }\n\n    pub fn take_next(&mut self) -> Option<PathBuf> {\n        loop {\n            match self.stack.pop() {\n                None => {\n                    break None;\n                }\n                Some(file_path) if !self.black_paths.contains(&file_path) => {\n                    let mut location = file_path.clone();\n                    location.pop();\n                    self.current_location = Some(location);\n                    self.black_paths.insert(file_path.clone());\n                    break Some(file_path);\n                }\n                _ => {}\n            }\n        }\n    }\n\n    pub fn is_user_input(&self, path: &PathBuf) -> bool {\n        self.user_inputs.contains(path)\n    }\n}\n"
  },
  {
    "path": "parser/src/lang.lalrpop",
    "content": "use num_bigint::BigInt;\nuse program_structure::statement_builders::*;\nuse program_structure::expression_builders::*;\nuse program_structure::ast::*;\nuse program_structure::ast_shortcuts::{self, Symbol, TupleInit};\nuse std::str::FromStr;\n\ngrammar;\n\n// ====================================================================\n// Body\n// ====================================================================\n\n// A identifier list is a comma separated list of identifiers\nIdentifierListDef : Vec<String> = {\n    <v:(<IDENTIFIER> \",\")*> <e:IDENTIFIER> => {\n        let mut v = v;\n        v.push(e);\n        v\n    }\n};\n\n// Pragma is included at the start of the file.\n// Their structure is the following: pragma circom \"version of the compiler\"\nParsePragma : Version = { // maybe change to usize instead of BigInt\n    \"pragma circom\" <version: Version>  \";\"\n    => version,\n};\n\n// Pragma to indicate that we are allowing the definition of custom templates.\nParseCustomGates : () = {\n    \"pragma\" \"custom_templates\" \";\" => ()\n}\n\n// Includes are added at the start of the file.\n// Their structure is the following: #include \"path to the file\"\nParseInclude : Include = {\n    <s:@L> \"include\" <file: STRING>  \";\" <e:@L>\n    => build_include(Meta::new(s, e), file),\n};\n\n// Parsing a program requires:\n// Parsing the version pragma, if there is one\n// Parsing the custom templates pragma, if there is one\n// Parsing \"includes\" instructions, if there is anyone\n// Parsing function and template definitions\n// Parsing the declaration of the main component\npub ParseAst : AST = {\n    <s:@L> <version: ParsePragma?> <custom_gates: ParseCustomGates?> <includes: ParseInclude*> <definitions: ParseDefinition*> <main: ParseMainComponent?> <e:@R>\n    => AST::new(Meta::new(s,e), version, custom_gates.is_some(), includes, definitions, main),\n};\n\n// ====================================================================\n// Definitions\n// ====================================================================\n\n// The private list of the main component stands for the\n// list of private input signals\nParsePublicList : Vec<String> = {\n    \"{\" \"public\" \"[\" <id: IdentifierListDef> \"]\" \"}\" => id,\n};\n\npub ParseMainComponent : MainComponent = {\n    <s:@L> \"component\" \"main\" <public_list: ParsePublicList?> \"=\" <init: ParseExpression> \";\" <e:@L>\n    => match public_list {\n        None => build_main_component(Vec::new(),init),\n        Some(list) => build_main_component(list,init)\n       },\n};\n\n\npub ParseDefinition : Definition = {\n    <s:@L> \"function\" <name: IDENTIFIER> \"(\" <args:@L> <arg_names: IdentifierListDef?>  <arge:@R> \")\" <body: ParseBlock> <e:@R>\n    => match arg_names {\n        None\n        => build_function(Meta::new(s,e),name,Vec::new(),args..arge,body),\n        Some(a)\n        => build_function(Meta::new(s,e),name,a,args..arge,body),\n    },\n    <s:@L> \"template\" <custom_gate: \"custom\"?> <parallel: \"parallel\"?> <name: IDENTIFIER> \"(\" <args:@L> <arg_names: IdentifierListDef?> <arge:@R> \")\" <body: ParseBlock> <e:@R>\n    => match arg_names {\n        None\n        => build_template(Meta::new(s,e), name, Vec::new(), args..arge, body, parallel.is_some(), custom_gate.is_some()),\n        Some(a)\n        => build_template(Meta::new(s,e), name, a, args..arge, body, parallel.is_some(), custom_gate.is_some()),\n    },\n};\n\n\n\n\n// ====================================================================\n// VariableDefinitions\n// ====================================================================\n\n// To generate the list of tags associated to a signal\nParseTagsList : Vec<String> = {\n    \"{\" <id: IdentifierListDef> \"}\" => id,\n};\n\nParseSignalType: SignalType = {\n    \"input\" => SignalType::Input,\n    \"output\" => SignalType::Output\n};\n\nSignalHeader : VariableType = {\n    \"signal\"  <signal_type: ParseSignalType?> <tags_list: ParseTagsList?>\n    => {\n        let s = match signal_type {\n            None => SignalType::Intermediate,\n            Some(st) => st,\n        };\n        let t = match tags_list {\n            None => Vec::new(),\n            Some(tl) => tl,\n        };\n        VariableType::Signal(s, t)\n    }\n};\n\n// ====================================================================\n// Statements\n// ====================================================================\n\n// A Initialization is either just the name of a variable or\n// the name followed by a expression that initialices the variable.\nTupleInitialization : TupleInit = {\n    \"<==\" <rhe: ParseExpression> => TupleInit {\n        tuple_init : (AssignOp::AssignConstraintSignal, rhe)\n    },\n    \"<--\" <rhe: ParseExpression> => TupleInit {\n        tuple_init : (AssignOp::AssignSignal, rhe)\n    },\n    \"=\" <rhe: ParseExpression> => TupleInit {\n        tuple_init : (AssignOp::AssignVar, rhe)\n    },\n}\n\nSimpleSymbol : Symbol = {\n    <name:IDENTIFIER> <dims:ParseArrayAcc*>\n    => Symbol {\n        name,\n        is_array: dims,\n        init: None,\n    },\n}\n\nComplexSymbol : Symbol = {\n    <name:IDENTIFIER> <dims:ParseArrayAcc*> \"=\" <rhe: ParseExpression>\n    => Symbol {\n        name,\n        is_array: dims,\n        init: Some(rhe),\n    },\n};\n\nSignalConstraintSymbol : Symbol = {\n    <name:IDENTIFIER> <dims:ParseArrayAcc*> \"<==\" <rhe: ParseExpression>\n    => Symbol {\n        name,\n        is_array: dims,\n        init: Some(rhe),\n    },\n};\n\nSignalSimpleSymbol : Symbol = {\n    <name:IDENTIFIER> <dims:ParseArrayAcc*> \"<--\" <rhe: ParseExpression>\n    => Symbol {\n        name,\n        is_array: dims,\n        init: Some(rhe),\n    },\n};\n\n\nSomeSymbol : Symbol = {\n    ComplexSymbol,\n    SimpleSymbol,\n}\n\nSignalSymbol : Symbol = {\n    SimpleSymbol,\n    SignalConstraintSymbol,\n}\n\n// A declaration is the definition of a type followed by the initialization\nParseDeclaration : Statement = {\n    <s:@L> \"var\" \"(\" <symbols:(<SimpleSymbol> \",\")*> <symbol: SimpleSymbol> \")\" <init : TupleInitialization?> <e:@R> => {\n        let mut symbols = symbols;\n        let meta = Meta::new(s, e);\n        let xtype = VariableType::Var;\n        symbols.push(symbol);\n        ast_shortcuts::split_declaration_into_single_nodes_and_multi_substitution(meta, xtype, symbols, init)\n    },\n    <s:@L> <xtype:SignalHeader> \"(\" <symbols:(<SimpleSymbol> \",\")*> <symbol: SimpleSymbol> \")\" <init : TupleInitialization?>  <e:@R> => {\n        let mut symbols = symbols;\n        let meta = Meta::new(s, e);\n        symbols.push(symbol);\n        ast_shortcuts::split_declaration_into_single_nodes_and_multi_substitution(meta, xtype, symbols, init)\n    },\n    <s:@L> \"component\" \"(\" <symbols:(<SimpleSymbol> \",\")*> <symbol: SimpleSymbol> \")\" <init : TupleInitialization?>  <e:@R> => {\n        let mut symbols = symbols;\n        let meta = Meta::new(s, e);\n        let xtype = VariableType::Component;\n        symbols.push(symbol);\n        ast_shortcuts::split_declaration_into_single_nodes_and_multi_substitution(meta, xtype, symbols, init)\n    },\n    <s:@L> \"var\" <symbols:(<SomeSymbol> \",\")*> <symbol: SomeSymbol> <e:@R> => {\n            let mut symbols = symbols;\n            let meta = Meta::new(s, e);\n            let xtype = VariableType::Var;\n            symbols.push(symbol);\n            ast_shortcuts::split_declaration_into_single_nodes(meta, xtype, symbols, AssignOp::AssignVar)\n    },\n    <s:@L> \"component\" <symbols:(<SomeSymbol> \",\")*> <symbol: SomeSymbol> <e:@R> => {\n            let mut symbols = symbols;\n            let meta = Meta::new(s, e);\n            let xtype = VariableType::Component;\n            symbols.push(symbol);\n            ast_shortcuts::split_declaration_into_single_nodes(meta, xtype, symbols, AssignOp::AssignVar)\n    },\n    <s:@L><xtype: SignalHeader> <symbols:(<SignalSymbol> \",\")*> <symbol: SignalSymbol>  <e:@R> => {\n            let mut symbols = symbols;\n            let meta = Meta::new(s, e);\n            symbols.push(symbol);\n            ast_shortcuts::split_declaration_into_single_nodes(meta,xtype,symbols,AssignOp::AssignConstraintSignal)\n    },\n    <s:@L><xtype: SignalHeader> <symbols:(<SignalSimpleSymbol> \",\")*> <symbol: SignalSimpleSymbol>  <e:@R> => {\n            let mut symbols = symbols;\n            let meta = Meta::new(s, e);\n            symbols.push(symbol);\n            ast_shortcuts::split_declaration_into_single_nodes(meta, xtype, symbols, AssignOp::AssignSignal)\n    },\n};\n\nParseSubstitution : Statement = {\n    <s:@L> <variable: ParseExpression> <ops: ParseAssignOp> <rhe: ParseExpression> <e:@R> => {\n        if let Expression::Variable {meta, name, access} = variable {\n            build_substitution(Meta::new(s, e), name, access, ops, rhe)\n        } else {\n            build_multi_substitution(Meta::new(s, e), variable, ops, rhe)\n        }\n    },\n    <s:@L> <lhe: ParseExpression> \"-->\" <variable: ParseExpression> <e:@R> => {\n        if let Expression::Variable {meta, name, access} = variable {\n            build_substitution(Meta::new(s, e), name, access, AssignOp::AssignSignal, lhe)\n        } else {\n            build_multi_substitution(Meta::new(s, e), variable, AssignOp::AssignSignal, lhe)\n        }\n    },\n    <s:@L> <lhe: ParseExpression> \"==>\" <variable: ParseExpression>  <e:@R> => {\n        if let Expression::Variable {meta, name, access} = variable {\n           build_substitution(Meta::new(s, e), name, access, AssignOp::AssignConstraintSignal, lhe)\n        } else{\n           build_multi_substitution(Meta::new(s, e), variable, AssignOp::AssignConstraintSignal, lhe)\n        }\n    },\n    <s:@L> <variable: ParseVariable>  \"\\\\=\" <rhe: ParseExpression> <e:@R> =>\n        ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::IntDiv, Meta::new(s, e), variable, rhe),\n\n    <s:@L> <variable: ParseVariable>  \"**=\" <rhe: ParseExpression> <e:@R> =>\n        ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::Pow, Meta::new(s, e), variable, rhe),\n\n    <s:@L> <variable: ParseVariable>  \"+=\" <rhe: ParseExpression> <e:@R> =>\n        ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::Add, Meta::new(s, e), variable, rhe),\n\n    <s:@L> <variable: ParseVariable>  \"-=\" <rhe: ParseExpression> <e:@R> =>\n        ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::Sub, Meta::new(s, e), variable, rhe),\n\n    <s:@L> <variable: ParseVariable>  \"*=\" <rhe: ParseExpression> <e:@R> =>\n        ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::Mul, Meta::new(s, e), variable, rhe),\n\n    <s:@L> <variable: ParseVariable>  \"/=\" <rhe: ParseExpression> <e:@R> =>\n        ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::Div, Meta::new(s, e), variable, rhe),\n\n    <s:@L> <variable: ParseVariable>  \"%=\" <rhe: ParseExpression> <e:@R> =>\n        ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::Mod, Meta::new(s, e), variable, rhe),\n\n    <s:@L> <variable: ParseVariable>  \"<<=\" <rhe: ParseExpression> <e:@R> =>\n        ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::ShiftL, Meta::new(s, e), variable, rhe),\n\n    <s:@L> <variable: ParseVariable>  \">>=\" <rhe: ParseExpression> <e:@R> =>\n        ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::ShiftR, Meta::new(s, e), variable, rhe),\n\n    <s:@L> <variable: ParseVariable>  \"&=\" <rhe: ParseExpression> <e:@R> =>\n        ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::BitAnd, Meta::new(s, e), variable, rhe),\n\n    <s:@L> <variable: ParseVariable>  \"|=\" <rhe: ParseExpression> <e:@R> =>\n        ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::BitOr, Meta::new(s, e), variable, rhe),\n\n    <s:@L> <variable: ParseVariable>  \"^=\" <rhe: ParseExpression> <e:@R> =>\n        ast_shortcuts::assign_with_op_shortcut(ExpressionInfixOpcode::BitXor, Meta::new(s, e), variable, rhe),\n\n    <s:@L> <variable: ParseVariable>  \"++\" <e:@R> =>\n        ast_shortcuts::plusplus(Meta::new(s,e),variable),\n\n    <s:@L> <variable: ParseVariable>  \"--\" <e:@R> =>\n        ast_shortcuts::subsub(Meta::new(s, e), variable),\n};\n\nParseBlock : Statement = {\n    <s:@L> \"{\" <stmts :ParseStatement3*> \"}\" <e:@R>\n     => build_block(Meta::new(s, e), stmts),\n};\n\npub ParseStatement : Statement = {\n    ParseStatement0\n};\n\nParseElse<StmtLevel> : Statement = {\n    \"else\" <else_case: StmtLevel> => else_case,\n};\n\nParseStatement0 : Statement = {\n    ParseStmt0NB,\n    ParseStatement1\n};\n\nParseStmt0NB : Statement = {\n    <s:@L> \"if\" \"(\" <cond: ParseExpression> \")\" <if_case: ParseStmt0NB> <e:@R> =>\n        build_conditional_block(Meta::new(s, e), cond, if_case, None),\n\n    <s:@L> \"if\" \"(\" <cond: ParseExpression> \")\" <if_case: ParseStatement1> <e:@R> =>\n        build_conditional_block(Meta::new(s, e), cond, if_case, None),\n\n    <s:@L> \"if\" \"(\" <cond: ParseExpression> \")\" <if_case: ParseStatement1> <else_case: ParseElse<ParseStmt0NB>><e:@R> =>\n        build_conditional_block(Meta::new(s, e), cond, if_case, Some(else_case)),\n};\n\nParseStatement1 : Statement = {\n    <s:@L> \"if\" \"(\" <cond: ParseExpression> \")\" <if_case: ParseStatement1> <else_case: ParseElse<ParseStatement1>><e:@R> =>\n        build_conditional_block(Meta::new(s, e), cond, if_case, Some(else_case)),\n\n    ParseStatement2\n};\n\nParseStatement2 : Statement = {\n    <s:@L> \"for\" \"(\" <init: ParseDeclaration> \";\" <cond: ParseExpression> \";\" <step: ParseSubstitution> \")\" <body: ParseStatement2> <e:@R> =>\n        ast_shortcuts::for_into_while(Meta::new(s, e), init, cond, step, body),\n\n    <s:@L> \"for\" \"(\" <init: ParseSubstitution> \";\" <cond: ParseExpression> \";\" <step: ParseSubstitution> \")\" <body: ParseStatement2> <e:@R> =>\n        ast_shortcuts::for_into_while(Meta::new(s, e), init, cond, step, body),\n\n    <s:@L>\"while\" \"(\" <cond: ParseExpression> \")\" <stmt: ParseStatement2> <e:@R> =>\n        build_while_block(Meta::new(s, e), cond, stmt),\n\n    <s:@L> \"return\" <value: ParseExpression> \";\"<e:@R> =>\n        build_return(Meta::new(s, e), value),\n\n    <subs: ParseSubstitution> \";\" =>\n        subs,\n\n    <s:@L> <lhe: ParseExpression> \"===\" <rhe: ParseExpression> \";\" <e:@R> =>\n        build_constraint_equality(Meta::new(s, e), lhe, rhe),\n\n    ParseStatementLog,\n\n    <s:@L> \"assert\" \"(\" <arg: ParseExpression> \")\" \";\" <e:@R> =>\n        build_assert(Meta::new(s,e),arg),\n\n    <s:@L> <lhe: ParseExpression> \";\" <e:@R> =>\n        build_anonymous_component_statement(Meta::new(s, e), lhe),\n\n    ParseBlock\n};\n\nParseStatementLog : Statement = {\n    <s:@L> \"log\" \"(\" <args: LogListable> \")\" \";\" <e:@R>\n    => build_log_call(Meta::new(s,e),args),\n\n    <s:@L> \"log\" \"(\" \")\" \";\" <e:@R>\n    => build_log_call(Meta::new(s,e),Vec::new()),\n};\n\nParseStatement3 : Statement = {\n    <dec: ParseDeclaration> \";\"\n    => dec,\n\n    ParseStatement\n};\n\n\n\n\n// ====================================================================\n// Variable\n// ====================================================================\n\nParseVarAccess : Access  = {\n    <arr_dec: ParseArrayAcc> => build_array_access(arr_dec),\n    <component_acc: ParseComponentAcc> => build_component_access(component_acc),\n};\n\nParseArrayAcc: Expression = {\n    \"[\"<dim: ParseExpression>\"]\" => dim\n};\n\nParseComponentAcc: String = {\n    \".\" <id: IDENTIFIER> => id,\n};\n\nParseVariable : (String,Vec<Access>) = {\n    <name:IDENTIFIER> <access: ParseVarAccess*>\n        => (name, access),\n};\n\n// ====================================================================\n// Expression\n// ====================================================================\n\nListable: Vec<Expression> = {\n    <e:(<ParseExpression> \",\")*> <tail: ParseExpression> => {\n        let mut e = e;\n        e.push(tail);\n        e\n    },\n};\n\nListableWithInputNames  : (Vec<Expression>,Option<Vec<(AssignOp,String)>>) = {\n    <e : (< IDENTIFIER> <ParseAssignOp> < ParseExpression> \",\")*>\n    <name: IDENTIFIER> <op : ParseAssignOp> <signal: ParseExpression> => {\n        let (mut operators_names, mut signals) = unzip_3(e);\n        signals.push(signal);\n        match operators_names.len() {\n            0 => (signals, Option::None),\n            _ => { operators_names.push((op,name)); (signals, Option::Some(operators_names))\n            }\n        }\n    }\n};\n\nListableAnon : (Vec<Expression>,Option<Vec<(AssignOp,String)>>) = {\n    <l : Listable> => {\n        (l, Option::None)\n    },\n\n    <l : ListableWithInputNames> =>\n        l,\n};\n\nParseString : LogArgument = {\n     <e: STRING> =>\n        build_log_string(e),\n};\n\nParseLogExp: LogArgument = {\n    <e : ParseExpression> =>\n        build_log_expression(e),\n}\n\nParseLogArgument : LogArgument = {\n    ParseLogExp,\n    ParseString\n};\n\nLogListable: Vec<LogArgument> = {\n    <e:(<ParseLogArgument> \",\")*> <tail: ParseLogArgument> => {\n        let mut e = e;\n        e.push(tail);\n        e\n    },\n};\n\nTwoElemsListable: Vec<Expression> = {\n    <head: ParseExpression> \",\" <head1: ParseExpression> <rest: (\",\" <ParseExpression>)*>\n    => {\n        let mut rest = rest;\n        let mut new_v = vec![head, head1];\n        new_v.append(&mut rest);\n        new_v\n    },\n};\n\nInfixOpTier<Op,NextTier> : Expression = {\n    <s:@L> <lhe:InfixOpTier<Op,NextTier>> <infix_op:Op> <rhe:NextTier> <e: @R> =>\n        build_infix(Meta::new(s, e), lhe, infix_op, rhe),\n\n    NextTier\n};\n\nPrefixOpTier<Op,NextTier >: Expression = {\n    <s:@L> <prefix_op:Op> <rhe:NextTier> <e:@R> =>\n        build_prefix(Meta::new(s, e), prefix_op, rhe),\n\n    NextTier\n};\n\npub ParseExpression: Expression = {\n    Expression14,\n    ParseExpression1,\n}\n\npub ParseExpression1: Expression = {\n    Expression13,\n    Expression12,\n};\n\n// parallel expr\nExpression14: Expression = {\n    <s:@L> \"parallel\" <expr: ParseExpression1> <e:@L>\n    => {\n        build_parallel_op(Meta::new(s, e), expr)\n    },\n\n}\n\n// ops: e ? a : i\nExpression13 : Expression  = {\n    <s:@L> <cond: Expression12>  \"?\" <if_true: Expression12> \":\" <if_false: Expression12> <e:@R>\n    => build_inline_switch_op(Meta::new(s, e), cond, if_true, if_false),\n};\n\n// ops: ||\nExpression12 = InfixOpTier<ParseBoolOr, Expression11>;\n\n// ops: &&\nExpression11 = InfixOpTier<ParseBoolAnd, Expression10>;\n\n// ops:  == != < > <= >=\nExpression10 = InfixOpTier<ParseCmpOpCodes, Expression9>;\n\n// ops: |\nExpression9 = InfixOpTier<ParseBitOr, Expression8>;\n\n// ops: ^\nExpression8 = InfixOpTier<ParseBitXOR, Expression7>;\n\n// ops: &\nExpression7 = InfixOpTier<ParseBitAnd, Expression6>;\n\n// ops: << >>\nExpression6 = InfixOpTier<ParseShift, Expression5>;\n\n// ops: + -\nExpression5 = InfixOpTier<ParseAddAndSub, Expression4>;\n\n// ops: * / \\\\ %\nExpression4 = InfixOpTier<ParseMulDiv, Expression3>;\n\n// ops: **\nExpression3 = InfixOpTier<ParseExp, Expression2>;\n\n// ops: Unary - ! ~\nExpression2 = PrefixOpTier<ParseExpressionPrefixOpcode, Expression1>;\n\n// function call, array inline, anonymous component call\nExpression1: Expression = {\n    <s:@L> <id: IDENTIFIER> \"(\" <args: Listable?> \")\" \"(\" <args2: ListableAnon?> \")\"  <e:@R> => {\n        let params = match args {\n            None => Vec::new(),\n            Some(a) => a\n        };\n        let (signals, names) = match args2 {\n            None => (Vec::new(),Option::None),\n            Some(a) => a\n        };\n        build_anonymous_component(Meta::new(s, e), id, params, signals, names, false)\n    },\n\n    <s:@L> <id: IDENTIFIER> \"(\" <args: Listable?> \")\" <e:@R> => match args {\n        None => build_call(Meta::new(s, e), id, Vec::new()),\n        Some(a) => build_call(Meta::new(s, e), id, a),\n    },\n\n    <s:@L> \"[\" <values: Listable> \"]\" <e:@R> =>\n        build_array_in_line(Meta::new(s, e), values),\n\n    <s:@L> \"(\" <values: TwoElemsListable> \")\" <e:@R> =>\n        build_tuple(Meta::new(s,e), values),\n\n    Expression0,\n};\n\n// Literal, parentheses\nExpression0: Expression = {\n    <s:@L> <variable: ParseVariable> <e:@L> => {\n        let (name, access) = variable;\n        build_variable(Meta::new(s, e), name, access)\n    },\n\n    <s:@L> \"_\" <e:@L> =>\n        build_variable(Meta::new(s, e), \"_\".to_string(), Vec::new()),\n\n\n    <s:@L> <value:DECNUMBER> <e:@L> =>\n        build_number(Meta::new(s, e), value),\n\n    <s:@L> <value:HEXNUMBER> <e:@L> =>\n        build_number(Meta::new(s, e), value),\n\n    \"(\" <ParseExpression> \")\"\n};\n\n// ====================================================================\n// Terminals\n// ====================================================================\n\n\n\nParseExpressionPrefixOpcode: ExpressionPrefixOpcode = {\n    \"!\" => ExpressionPrefixOpcode::BoolNot,\n    \"~\" => ExpressionPrefixOpcode::Complement,\n    \"-\" => ExpressionPrefixOpcode::Sub,\n};\n\nParseBoolOr : ExpressionInfixOpcode = {\n    \"||\" => ExpressionInfixOpcode::BoolOr,\n};\n\nParseBoolAnd : ExpressionInfixOpcode = {\n    \"&&\" => ExpressionInfixOpcode::BoolAnd,\n};\n\nParseCmpOpCodes : ExpressionInfixOpcode = {\n    \"==\" => ExpressionInfixOpcode::Eq,\n    \"!=\" => ExpressionInfixOpcode::NotEq,\n    \"<\"  => ExpressionInfixOpcode::Lesser,\n    \">\"  => ExpressionInfixOpcode::Greater,\n    \"<=\" => ExpressionInfixOpcode::LesserEq,\n    \">=\" => ExpressionInfixOpcode::GreaterEq,\n};\n\nParseBitOr : ExpressionInfixOpcode = {\n    \"|\" =>  ExpressionInfixOpcode::BitOr,\n};\n\nParseBitAnd : ExpressionInfixOpcode = {\n    \"&\" =>  ExpressionInfixOpcode::BitAnd,\n};\n\nParseShift : ExpressionInfixOpcode = {\n    \"<<\" => ExpressionInfixOpcode::ShiftL,\n    \">>\" => ExpressionInfixOpcode::ShiftR,\n};\n\nParseAddAndSub : ExpressionInfixOpcode = {\n    \"+\" =>  ExpressionInfixOpcode::Add,\n    \"-\" =>  ExpressionInfixOpcode::Sub,\n};\n\nParseMulDiv : ExpressionInfixOpcode = {\n    \"*\" =>  ExpressionInfixOpcode::Mul,\n    \"/\" =>  ExpressionInfixOpcode::Div,\n    \"\\\\\" => ExpressionInfixOpcode::IntDiv,\n    \"%\" =>  ExpressionInfixOpcode::Mod,\n};\n\nParseExp : ExpressionInfixOpcode = {\n    \"**\" => ExpressionInfixOpcode::Pow,\n};\n\nParseBitXOR : ExpressionInfixOpcode = {\n    \"^\" =>  ExpressionInfixOpcode::BitXor,\n};\n\n\nParseAssignOp: AssignOp = {\n    \"=\"   => AssignOp::AssignVar,\n    \"<--\" => AssignOp::AssignSignal,\n    \"<==\" => AssignOp::AssignConstraintSignal,\n};\n\nDECNUMBER: BigInt = {\n    r\"[0-9]+\" => BigInt::parse_bytes(&<>.as_bytes(),10).expect(\"failed to parse base10\")\n};\n\nHEXNUMBER : BigInt = {\n    r\"0x[0-9A-Fa-f]*\" => BigInt::parse_bytes(&(<>.as_bytes()[2..]),16).expect(\"failed to parse base16\")\n};\n\nIDENTIFIER : String = {\n    r\"[$_]*[a-zA-Z][a-zA-Z$_0-9]*\" => String::from(<>)\n};\n\nSTRING : String = {\n    <s:r#\"\"[^\"]*\"\"#> => String::from(&s[1..s.len()-1])\n};\n\nSMALL_DECNUMBER: usize = {\n    r\"[0-9]+\" => usize::from_str(<>).expect(\"failed to parse number\")\n};\n\n\n// Version used by pragma to describe the compiler, its syntax is Number1.Number2.Number3...\nVersion : Version = {\n    <version: SMALL_DECNUMBER> \".\" <subversion:SMALL_DECNUMBER> \".\" <subsubversion:SMALL_DECNUMBER> => {\n        (version, subversion, subsubversion)\n    }\n};\n"
  },
  {
    "path": "parser/src/lib.rs",
    "content": "extern crate num_bigint_dig as num_bigint;\nextern crate num_traits;\nextern crate serde;\nextern crate serde_derive;\n\n#[macro_use]\nextern crate lalrpop_util;\n\n// Silence clippy warnings for generated code.\nlalrpop_mod!(#[allow(clippy::all)] pub lang);\n\nuse log::debug;\n\nmod errors;\nmod include_logic;\nmod parser_logic;\nmod syntax_sugar_traits;\nmod syntax_sugar_remover;\n\npub use parser_logic::parse_definition;\n\nuse include_logic::FileStack;\nuse program_structure::ast::{Version, AST};\nuse program_structure::report::{Report, ReportCollection};\nuse program_structure::file_definition::{FileID, FileLibrary};\nuse program_structure::program_archive::ProgramArchive;\nuse program_structure::template_library::TemplateLibrary;\nuse std::collections::HashMap;\nuse std::path::{Path, PathBuf};\n\n/// A result from the Circom parser.\npub enum ParseResult {\n    /// The program was successfully parsed without issues.\n    Program(Box<ProgramArchive>, ReportCollection),\n    /// The parser failed to parse a complete program.\n    Library(Box<TemplateLibrary>, ReportCollection),\n}\n\npub fn parse_files(\n    file_paths: &[PathBuf],\n    libraries: &[PathBuf],\n    compiler_version: &Version,\n) -> ParseResult {\n    let mut reports = ReportCollection::new();\n    let mut file_stack = FileStack::new(file_paths, libraries, &mut reports);\n    let mut file_library = FileLibrary::new();\n    let mut definitions = HashMap::new();\n    let mut main_components = Vec::new();\n    while let Some(file_path) = FileStack::take_next(&mut file_stack) {\n        match parse_file(&file_path, &mut file_stack, &mut file_library, compiler_version) {\n            Ok((file_id, program, mut warnings)) => {\n                if let Some(main_component) = program.main_component {\n                    main_components.push((file_id, main_component, program.custom_gates));\n                }\n                debug!(\n                    \"adding {} definitions from `{}`\",\n                    program.definitions.iter().map(|x| x.name()).collect::<Vec<_>>().join(\", \"),\n                    file_path.display(),\n                );\n                definitions.insert(file_id, program.definitions);\n                reports.append(&mut warnings);\n            }\n            Err(error) => {\n                reports.push(*error);\n            }\n        }\n    }\n    // Create a parse result.\n    let mut result = match &main_components[..] {\n        [(main_id, main_component, custom_gates)] => {\n            // TODO: This calls FillMeta::fill a second time.\n            match ProgramArchive::new(\n                file_library,\n                *main_id,\n                main_component,\n                &definitions,\n                *custom_gates,\n            ) {\n                Ok(program_archive) => ParseResult::Program(Box::new(program_archive), reports),\n                Err((file_library, mut errors)) => {\n                    reports.append(&mut errors);\n                    let template_library = TemplateLibrary::new(definitions, file_library);\n                    ParseResult::Library(Box::new(template_library), reports)\n                }\n            }\n        }\n        [] => {\n            // TODO: Maybe use a flag to ensure that a main component must be present.\n            let template_library = TemplateLibrary::new(definitions, file_library);\n            ParseResult::Library(Box::new(template_library), reports)\n        }\n        _ => {\n            reports.push(errors::MultipleMainError::produce_report());\n            let template_library = TemplateLibrary::new(definitions, file_library);\n            ParseResult::Library(Box::new(template_library), reports)\n        }\n    };\n    // Remove anonymous components and tuples.\n    //\n    // TODO: This could be moved to the lifting phase.\n    match &mut result {\n        ParseResult::Program(program_archive, reports) => {\n            if program_archive.main_expression().is_anonymous_component() {\n                reports.push(\n                    errors::AnonymousComponentError::new(\n                        Some(program_archive.main_expression().meta()),\n                        \"The main component cannot contain an anonymous call.\",\n                        Some(\"Main component defined here.\"),\n                    )\n                    .into_report(),\n                );\n            }\n            let (new_templates, new_functions) = syntax_sugar_remover::remove_syntactic_sugar(\n                &program_archive.templates,\n                &program_archive.functions,\n                &program_archive.file_library,\n                reports,\n            );\n            program_archive.templates = new_templates;\n            program_archive.functions = new_functions;\n        }\n        ParseResult::Library(template_library, reports) => {\n            let (new_templates, new_functions) = syntax_sugar_remover::remove_syntactic_sugar(\n                &template_library.templates,\n                &template_library.functions,\n                &template_library.file_library,\n                reports,\n            );\n            template_library.templates = new_templates;\n            template_library.functions = new_functions;\n        }\n    }\n    result\n}\n\npub fn parse_file(\n    file_path: &PathBuf,\n    file_stack: &mut FileStack,\n    file_library: &mut FileLibrary,\n    compiler_version: &Version,\n) -> Result<(FileID, AST, ReportCollection), Box<Report>> {\n    let mut reports = ReportCollection::new();\n\n    debug!(\"reading file `{}`\", file_path.display());\n    let (path_str, file_content) = open_file(file_path)?;\n    let is_user_input = file_stack.is_user_input(file_path);\n    let file_id = file_library.add_file(path_str, file_content.clone(), is_user_input);\n\n    debug!(\"parsing file `{}`\", file_path.display());\n    let program = parser_logic::parse_file(&file_content, file_id)?;\n    match check_compiler_version(file_path, program.compiler_version, compiler_version) {\n        Ok(warnings) => reports.extend(warnings),\n        Err(error) => reports.push(*error),\n    }\n    for include in &program.includes {\n        if let Err(report) = file_stack.add_include(include) {\n            reports.push(*report);\n        }\n    }\n    Ok((file_id, program, reports))\n}\n\nfn open_file(file_path: &PathBuf) -> Result<(String, String), Box<Report>> /* path, src*/ {\n    use errors::FileOsError;\n    use std::fs::read_to_string;\n    let path_str = format!(\"{}\", file_path.display());\n    read_to_string(file_path)\n        .map(|contents| (path_str.clone(), contents))\n        .map_err(|_| FileOsError { path: path_str.clone() })\n        .map_err(|error| Box::new(error.into_report()))\n}\n\nfn check_compiler_version(\n    file_path: &Path,\n    required_version: Option<Version>,\n    compiler_version: &Version,\n) -> Result<ReportCollection, Box<Report>> {\n    use errors::{CompilerVersionError, NoCompilerVersionWarning};\n    if let Some(required_version) = required_version {\n        if (required_version.0 == compiler_version.0 && required_version.1 < compiler_version.1)\n            || (required_version.0 == compiler_version.0\n                && required_version.1 == compiler_version.1\n                && required_version.2 <= compiler_version.2)\n        {\n            Ok(vec![])\n        } else {\n            let error = CompilerVersionError {\n                path: format!(\"{}\", file_path.display()),\n                required_version,\n                version: *compiler_version,\n            };\n            Err(Box::new(error.into_report()))\n        }\n    } else {\n        let report = NoCompilerVersionWarning::produce_report(NoCompilerVersionWarning {\n            path: format!(\"{}\", file_path.display()),\n            version: *compiler_version,\n        });\n        Ok(vec![report])\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::path::PathBuf;\n\n    use crate::check_compiler_version;\n\n    #[test]\n    fn test_compiler_version() {\n        let path = PathBuf::from(\"example.circom\");\n\n        assert!(check_compiler_version(&path, None, &(2, 1, 2)).is_ok());\n        assert!(check_compiler_version(&path, Some((2, 0, 0)), &(2, 1, 2)).is_ok());\n        assert!(check_compiler_version(&path, Some((2, 0, 8)), &(2, 1, 2)).is_ok());\n        assert!(check_compiler_version(&path, Some((2, 1, 2)), &(2, 1, 2)).is_ok());\n\n        // We don't support Circom 1.\n        assert!(check_compiler_version(&path, Some((1, 0, 0)), &(2, 0, 8)).is_err());\n        assert!(check_compiler_version(&path, Some((2, 1, 2)), &(2, 0, 8)).is_err());\n        assert!(check_compiler_version(&path, Some((2, 1, 4)), &(2, 1, 2)).is_err());\n    }\n}\n"
  },
  {
    "path": "parser/src/parser_logic.rs",
    "content": "use super::errors::{ParsingError, UnclosedCommentError};\nuse super::lang;\n\nuse program_structure::ast::AST;\nuse program_structure::report::Report;\nuse program_structure::file_definition::FileID;\n\npub fn preprocess(expr: &str, file_id: FileID) -> Result<String, Box<Report>> {\n    let mut pp = String::new();\n    let mut state = 0;\n    let mut loc = 0;\n    let mut block_start = 0;\n\n    let mut it = expr.chars();\n    while let Some(c0) = it.next() {\n        loc += 1;\n        match (state, c0) {\n            (0, '/') => {\n                loc += 1;\n                match it.next() {\n                    Some('/') => {\n                        state = 1;\n                        pp.push(' ');\n                        pp.push(' ');\n                    }\n                    Some('*') => {\n                        block_start = loc;\n                        state = 2;\n                        pp.push(' ');\n                        pp.push(' ');\n                    }\n                    Some(c1) => {\n                        pp.push(c0);\n                        pp.push(c1);\n                    }\n                    None => {\n                        pp.push(c0);\n                        break;\n                    }\n                }\n            }\n            (0, _) => pp.push(c0),\n            (1, '\\n') => {\n                pp.push(c0);\n                state = 0;\n            }\n            (2, '*') => {\n                loc += 1;\n                match it.next() {\n                    Some('/') => {\n                        pp.push(' ');\n                        pp.push(' ');\n                        state = 0;\n                    }\n                    Some(c) => {\n                        pp.push(' ');\n                        for _i in 0..c.len_utf8() {\n                            pp.push(' ');\n                        }\n                    }\n                    None => {\n                        let error =\n                            UnclosedCommentError { location: block_start..block_start, file_id };\n                        return Err(Box::new(error.into_report()));\n                    }\n                }\n            }\n            (_, c) => {\n                for _i in 0..c.len_utf8() {\n                    pp.push(' ');\n                }\n            }\n        }\n    }\n    Ok(pp)\n}\n\npub fn parse_file(src: &str, file_id: FileID) -> Result<AST, Box<Report>> {\n    use lalrpop_util::ParseError::*;\n    lang::ParseAstParser::new()\n        .parse(&preprocess(src, file_id)?)\n        .map(|mut ast| {\n            // Set file ID for better error reporting.\n            for include in &mut ast.includes {\n                include.meta.set_file_id(file_id);\n            }\n            ast\n        })\n        .map_err(|parse_error| match parse_error {\n            InvalidToken { location } => ParsingError {\n                file_id,\n                message: \"Invalid token found.\".to_string(),\n                location: location..location,\n            },\n            UnrecognizedToken { ref token, ref expected } => ParsingError {\n                file_id,\n                message: format!(\n                    \"Unrecognized token `{}` found.{}\",\n                    token.1,\n                    format_expected(expected)\n                ),\n                location: token.0..token.2,\n            },\n            ExtraToken { ref token } => ParsingError {\n                file_id,\n                message: format!(\"Extra token `{}` found.\", token.2),\n                location: token.0..token.2,\n            },\n            _ => ParsingError { file_id, message: format!(\"{parse_error}\"), location: 0..0 },\n        })\n        .map_err(|error| Box::new(error.into_report()))\n}\n\npub fn parse_string(src: &str) -> Option<AST> {\n    let src = preprocess(src, 0).ok()?;\n    lang::ParseAstParser::new().parse(&src).ok()\n}\n\n/// Parse a single (function or template) definition for testing purposes.\nuse program_structure::ast::Definition;\n\npub fn parse_definition(src: &str) -> Option<Definition> {\n    match parse_string(src) {\n        Some(AST { mut definitions, .. }) if definitions.len() == 1 => definitions.pop(),\n        _ => None,\n    }\n}\n\n#[must_use]\nfn format_expected(tokens: &[String]) -> String {\n    if tokens.is_empty() {\n        String::new()\n    } else {\n        let tokens = tokens\n            .iter()\n            .enumerate()\n            .map(|(index, token)| {\n                if index == 0 {\n                    token.replace('\\\"', \"`\")\n                } else if index < tokens.len() - 1 {\n                    format!(\", {}\", token.replace('\\\"', \"`\"))\n                } else {\n                    format!(\" or {}\", token.replace('\\\"', \"`\"))\n                }\n            })\n            .collect::<Vec<_>>()\n            .join(\"\");\n        format!(\" Expected one of {tokens}.\")\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::parse_string;\n\n    #[test]\n    fn test_parse_string() {\n        let function = r#\"\n            function f(m) {\n                // This is a comment.\n                var x = 1024;\n                var y = 16;\n                while (x < m) {\n                    x += y;\n                }\n                if (x == m) {\n                    x = 0;\n                }\n                /* This is another comment. */\n                return x;\n            }\n        \"#;\n        let _ = parse_string(function);\n\n        let template = r#\"\n            template T(m) {\n                signal input in[m];\n                signal output out;\n\n                var sum = 0;\n                for (var i = 0; i < m; i++) {\n                    sum += in[i];\n                }\n                out <== sum;\n            }\n        \"#;\n        let _ = parse_string(template);\n    }\n}\n"
  },
  {
    "path": "parser/src/syntax_sugar_remover.rs",
    "content": "use program_structure::ast::*;\nuse program_structure::statement_builders::{build_block, build_substitution};\nuse program_structure::report::{Report, ReportCollection};\nuse program_structure::expression_builders::{build_call, build_tuple, build_parallel_op};\nuse program_structure::file_definition::FileLibrary;\nuse program_structure::statement_builders::{\n    build_declaration, build_log_call, build_assert, build_return, build_constraint_equality,\n    build_initialization_block,\n};\nuse program_structure::template_data::TemplateData;\nuse program_structure::function_data::FunctionData;\nuse std::collections::HashMap;\nuse num_bigint::BigInt;\n\nuse crate::errors::{AnonymousComponentError, TupleError};\nuse crate::syntax_sugar_traits::ContainsExpression;\n\n/// This functions desugars all anonymous components and tuples.\n#[must_use]\npub(crate) fn remove_syntactic_sugar(\n    templates: &HashMap<String, TemplateData>,\n    functions: &HashMap<String, FunctionData>,\n    file_library: &FileLibrary,\n    reports: &mut ReportCollection,\n) -> (HashMap<String, TemplateData>, HashMap<String, FunctionData>) {\n    // Remove anonymous components and tuples from templates.\n    let mut new_templates = HashMap::new();\n    for (name, template) in templates {\n        let body = template.get_body().clone();\n        let (new_body, declarations) =\n            match remove_anonymous_from_statement(templates, file_library, body, &None) {\n                Ok(result) => result,\n                Err(report) => {\n                    // If we encounter an error we simply report the error and continue.\n                    // This means that the template is dropped and no more analysis is\n                    // performed on it.\n                    //\n                    // TODO: If we want to do inter-procedural analysis we need to track\n                    // removed templates.\n                    reports.push(*report);\n                    continue;\n                }\n            };\n        if let Statement::Block { meta, mut stmts } = new_body {\n            let (component_decs, variable_decs, mut substitutions) =\n                separate_declarations_in_comp_var_subs(declarations);\n            let mut init_block = vec![\n                build_initialization_block(meta.clone(), VariableType::Var, variable_decs),\n                build_initialization_block(meta.clone(), VariableType::Component, component_decs),\n            ];\n            init_block.append(&mut substitutions);\n            init_block.append(&mut stmts);\n            let new_body_with_inits = build_block(meta, init_block);\n            let new_body = match remove_tuples_from_statement(new_body_with_inits) {\n                Ok(result) => result,\n                Err(report) => {\n                    // If we encounter an error we simply report the error and continue.\n                    // This means that the template is dropped and no more analysis is\n                    // performed on it.\n                    //\n                    // TODO: If we want to do inter-procedural analysis we need to track\n                    // removed templates.\n                    reports.push(*report);\n                    continue;\n                }\n            };\n            let mut new_template = template.clone();\n            *new_template.get_mut_body() = new_body;\n            new_templates.insert(name.clone(), new_template);\n        } else {\n            unreachable!()\n        }\n    }\n\n    // Drop any functions containing anonymous components or tuples.\n    let mut new_functions = HashMap::new();\n    for (name, function) in functions {\n        let body = function.get_body();\n        if body.contains_tuple(Some(reports)) {\n            continue;\n        }\n        if body.contains_anonymous_component(Some(reports)) {\n            continue;\n        }\n        new_functions.insert(name.clone(), function.clone());\n    }\n    (new_templates, new_functions)\n}\n\nfn remove_anonymous_from_statement(\n    templates: &HashMap<String, TemplateData>,\n    file_library: &FileLibrary,\n    stmt: Statement,\n    var_access: &Option<Expression>,\n) -> Result<(Statement, Vec<Statement>), Box<Report>> {\n    match stmt {\n        Statement::MultiSubstitution { meta, lhe, op, rhe } => {\n            if lhe.contains_anonymous_component(None) {\n                return Err(AnonymousComponentError::boxed_report(\n                    lhe.meta(),\n                    \"An anonymous component cannot occur as the left-hand side of an assignment\",\n                ));\n            } else {\n                let (mut stmts, declarations, new_rhe) =\n                    remove_anonymous_from_expression(templates, file_library, rhe, var_access)?;\n                let subs =\n                    Statement::MultiSubstitution { meta: meta.clone(), lhe, op, rhe: new_rhe };\n                let mut substs = Vec::new();\n                if stmts.is_empty() {\n                    Ok((subs, declarations))\n                } else {\n                    substs.append(&mut stmts);\n                    substs.push(subs);\n                    Ok((Statement::Block { meta, stmts: substs }, declarations))\n                }\n            }\n        }\n        Statement::IfThenElse { meta, cond, if_case, else_case } => {\n            if cond.contains_anonymous_component(None) {\n                Err(AnonymousComponentError::boxed_report(\n                    &meta,\n                    \"Anonymous components cannot be used inside conditions.\",\n                ))\n            } else {\n                let (new_if_case, mut declarations) =\n                    remove_anonymous_from_statement(templates, file_library, *if_case, var_access)?;\n                match else_case {\n                    Some(else_case) => {\n                        let (new_else_case, mut new_declarations) =\n                            remove_anonymous_from_statement(\n                                templates,\n                                file_library,\n                                *else_case,\n                                var_access,\n                            )?;\n                        declarations.append(&mut new_declarations);\n                        Ok((\n                            Statement::IfThenElse {\n                                meta,\n                                cond,\n                                if_case: Box::new(new_if_case),\n                                else_case: Some(Box::new(new_else_case)),\n                            },\n                            declarations,\n                        ))\n                    }\n                    None => Ok((\n                        Statement::IfThenElse {\n                            meta,\n                            cond,\n                            if_case: Box::new(new_if_case),\n                            else_case: None,\n                        },\n                        declarations,\n                    )),\n                }\n            }\n        }\n        Statement::While { meta, cond, stmt } => {\n            if cond.contains_anonymous_component(None) {\n                return Err(AnonymousComponentError::boxed_report(\n                    cond.meta(),\n                    \"Anonymous components cannot be used inside conditions.\",\n                ));\n            } else {\n                let id_var_while = \"anon_var_\".to_string()\n                    + &file_library.get_line(meta.start, meta.get_file_id()).unwrap().to_string()\n                    + \"_\"\n                    + &meta.start.to_string();\n                let var_access = Expression::Variable {\n                    meta: meta.clone(),\n                    name: id_var_while.clone(),\n                    access: Vec::new(),\n                };\n                let mut declarations = vec![];\n                let (new_stmt, mut new_declarations) = remove_anonymous_from_statement(\n                    templates,\n                    file_library,\n                    *stmt,\n                    &Some(var_access.clone()),\n                )?;\n                let boxed_stmt = if !new_declarations.is_empty() {\n                    declarations.push(build_declaration(\n                        meta.clone(),\n                        VariableType::Var,\n                        id_var_while.clone(),\n                        Vec::new(),\n                    ));\n                    declarations.push(build_substitution(\n                        meta.clone(),\n                        id_var_while.clone(),\n                        vec![],\n                        AssignOp::AssignVar,\n                        Expression::Number(meta.clone(), BigInt::from(0)),\n                    ));\n                    declarations.append(&mut new_declarations);\n                    let next_access = Expression::InfixOp {\n                        meta: meta.clone(),\n                        infix_op: ExpressionInfixOpcode::Add,\n                        lhe: Box::new(var_access),\n                        rhe: Box::new(Expression::Number(meta.clone(), BigInt::from(1))),\n                    };\n                    let subs_access = Statement::Substitution {\n                        meta: meta.clone(),\n                        var: id_var_while,\n                        access: Vec::new(),\n                        op: AssignOp::AssignVar,\n                        rhe: next_access,\n                    };\n\n                    let new_block =\n                        Statement::Block { meta: meta.clone(), stmts: vec![new_stmt, subs_access] };\n                    Box::new(new_block)\n                } else {\n                    Box::new(new_stmt)\n                };\n\n                Ok((Statement::While { meta, cond, stmt: boxed_stmt }, declarations))\n            }\n        }\n        Statement::LogCall { meta, args } => {\n            for arg in &args {\n                if let program_structure::ast::LogArgument::LogExp(exp) = arg {\n                    if exp.contains_anonymous_component(None) {\n                        return Err(AnonymousComponentError::boxed_report(\n                            &meta,\n                            \"An anonymous component cannot be used inside a log statement.\",\n                        ));\n                    }\n                }\n            }\n            Ok((build_log_call(meta, args), Vec::new()))\n        }\n        Statement::Assert { meta, arg } => Ok((build_assert(meta, arg), Vec::new())),\n        Statement::Return { meta, value: arg } => {\n            if arg.contains_anonymous_component(None) {\n                Err(AnonymousComponentError::boxed_report(\n                    &meta,\n                    \"An anonymous component cannot be used as a return value.\",\n                ))\n            } else {\n                Ok((build_return(meta, arg), Vec::new()))\n            }\n        }\n        Statement::ConstraintEquality { meta, lhe, rhe } => {\n            if lhe.contains_anonymous_component(None) || rhe.contains_anonymous_component(None) {\n                Err(AnonymousComponentError::boxed_report(\n                    &meta,\n                    \"Anonymous components cannot be used together with the constraint equality operator `===`.\",\n                ))\n            } else {\n                Ok((build_constraint_equality(meta, lhe, rhe), Vec::new()))\n            }\n        }\n        Statement::Declaration { meta, xtype, name, dimensions, .. } => {\n            for exp in dimensions.clone() {\n                if exp.contains_anonymous_component(None) {\n                    return Err(AnonymousComponentError::boxed_report(\n                        exp.meta(),\n                        \"An anonymous component cannot be used to define the dimensions of an array.\",\n                    ));\n                }\n            }\n            Ok((build_declaration(meta, xtype, name, dimensions), Vec::new()))\n        }\n        Statement::InitializationBlock { meta, xtype, initializations } => {\n            let mut new_inits = Vec::new();\n            let mut declarations = Vec::new();\n            for stmt in initializations {\n                let (stmt_ok, mut declaration) =\n                    remove_anonymous_from_statement(templates, file_library, stmt, var_access)?;\n                new_inits.push(stmt_ok);\n                declarations.append(&mut declaration)\n            }\n            Ok((\n                Statement::InitializationBlock { meta, xtype, initializations: new_inits },\n                declarations,\n            ))\n        }\n        Statement::Block { meta, stmts } => {\n            let mut new_stmts = Vec::new();\n            let mut declarations = Vec::new();\n            for stmt in stmts {\n                let (stmt_ok, mut declaration) =\n                    remove_anonymous_from_statement(templates, file_library, stmt, var_access)?;\n                new_stmts.push(stmt_ok);\n                declarations.append(&mut declaration);\n            }\n            Ok((Statement::Block { meta, stmts: new_stmts }, declarations))\n        }\n        Statement::Substitution { meta, var, op, rhe, access } => {\n            let (mut stmts, declarations, new_rhe) =\n                remove_anonymous_from_expression(templates, file_library, rhe, var_access)?;\n            let subs =\n                Statement::Substitution { meta: meta.clone(), var, access, op, rhe: new_rhe };\n            let mut substs = Vec::new();\n            if stmts.is_empty() {\n                Ok((subs, declarations))\n            } else {\n                substs.append(&mut stmts);\n                substs.push(subs);\n                Ok((Statement::Block { meta, stmts: substs }, declarations))\n            }\n        }\n    }\n}\n\n// returns a block with the substitutions, the declarations and finally the output expression\nfn remove_anonymous_from_expression(\n    templates: &HashMap<String, TemplateData>,\n    file_library: &FileLibrary,\n    expr: Expression,\n    var_access: &Option<Expression>, // in case the call is inside a loop, variable used to control the access\n) -> Result<(Vec<Statement>, Vec<Statement>, Expression), Box<Report>> {\n    use Expression::*;\n    match expr.clone() {\n        ArrayInLine { values, .. } => {\n            for value in values {\n                if value.contains_anonymous_component(None) {\n                    return Err(AnonymousComponentError::boxed_report(\n                        value.meta(),\n                        \"An anonymous component cannot be used to define the dimensions of an array.\",\n                    ));\n                }\n            }\n            Ok((Vec::new(), Vec::new(), expr))\n        }\n        Number(_, _) => Ok((Vec::new(), Vec::new(), expr)),\n        Variable { meta, .. } => {\n            if expr.contains_anonymous_component(None) {\n                return Err(AnonymousComponentError::boxed_report(\n                    &meta,\n                    \"An anonymous component cannot be used to access an array.\",\n                ));\n            }\n            Ok((Vec::new(), Vec::new(), expr))\n        }\n        InfixOp { meta, lhe, rhe, .. } => {\n            if lhe.contains_anonymous_component(None) || rhe.contains_anonymous_component(None) {\n                return Err(AnonymousComponentError::boxed_report(\n                    &meta,\n                    \"Anonymous components cannot be used in arithmetic or boolean expressions.\",\n                ));\n            }\n            Ok((Vec::new(), Vec::new(), expr))\n        }\n        PrefixOp { meta, rhe, .. } => {\n            if rhe.contains_anonymous_component(None) {\n                return Err(AnonymousComponentError::boxed_report(\n                    &meta,\n                    \"Anonymous components cannot be used in arithmetic or boolean expressions.\",\n                ));\n            }\n            Ok((Vec::new(), Vec::new(), expr))\n        }\n        InlineSwitchOp { meta, cond, if_true, if_false } => {\n            if cond.contains_anonymous_component(None)\n                || if_true.contains_anonymous_component(None)\n                || if_false.contains_anonymous_component(None)\n            {\n                return Err(AnonymousComponentError::boxed_report(\n                    &meta,\n                    \"An anonymous component cannot be used inside an inline switch expression.\",\n                ));\n            }\n            Ok((Vec::new(), Vec::new(), expr))\n        }\n        Call { meta, args, .. } => {\n            for value in args {\n                if value.contains_anonymous_component(None) {\n                    return Err(AnonymousComponentError::boxed_report(\n                        &meta,\n                        \"An anonymous component cannot be used as an argument to a template call.\",\n                    ));\n                }\n            }\n            Ok((Vec::new(), Vec::new(), expr))\n        }\n        AnonymousComponent { meta, id, params, signals, names, is_parallel } => {\n            let template = templates.get(&id);\n            let mut declarations = Vec::new();\n            if template.is_none() {\n                return Err(Box::new(\n                    AnonymousComponentError::new(\n                        Some(&meta),\n                        &format!(\"The template `{id}` does not exist.\"),\n                        Some(&format!(\"Unknown template `{id}` instantiated here.\")),\n                    )\n                    .into_report(),\n                ));\n            }\n            let mut i = 0;\n            let mut seq_substs = Vec::new();\n            let id_anon_temp = id.to_string()\n                + \"_\"\n                + &file_library.get_line(meta.start, meta.get_file_id()).unwrap().to_string()\n                + \"_\"\n                + &meta.start.to_string();\n            if var_access.is_none() {\n                declarations.push(build_declaration(\n                    meta.clone(),\n                    VariableType::Component,\n                    id_anon_temp.clone(),\n                    Vec::new(),\n                ));\n            } else {\n                declarations.push(build_declaration(\n                    meta.clone(),\n                    VariableType::AnonymousComponent,\n                    id_anon_temp.clone(),\n                    vec![var_access.as_ref().unwrap().clone()],\n                ));\n            }\n            let call = build_call(meta.clone(), id, params);\n            if call.contains_anonymous_component(None) {\n                return Err(AnonymousComponentError::boxed_report(\n                    &meta,\n                    \"An anonymous component cannot be used as a argument to a template call.\",\n                ));\n            }\n\n            let exp_with_call =\n                if is_parallel { build_parallel_op(meta.clone(), call) } else { call };\n            let access = if var_access.is_none() {\n                Vec::new()\n            } else {\n                vec![build_array_access(var_access.as_ref().unwrap().clone())]\n            };\n            let sub = build_substitution(\n                meta.clone(),\n                id_anon_temp.clone(),\n                access,\n                AssignOp::AssignVar,\n                exp_with_call,\n            );\n            seq_substs.push(sub);\n            let inputs = template.unwrap().get_declaration_inputs();\n            let mut new_signals = Vec::new();\n            let mut new_operators = Vec::new();\n            if let Some(m) = names {\n                let (operators, names): (Vec<AssignOp>, Vec<String>) = m.iter().cloned().unzip();\n                for inp in inputs {\n                    if !names.contains(&inp.0) {\n                        return Err(AnonymousComponentError::boxed_report(\n                            &meta,\n                            &format!(\"The input signal `{}` is not assigned by the anonymous component call.\", inp.0),\n                        ));\n                    } else {\n                        let pos = names.iter().position(|r| *r == inp.0).unwrap();\n                        new_signals.push(signals.get(pos).unwrap().clone());\n                        new_operators.push(*operators.get(pos).unwrap());\n                    }\n                }\n            } else {\n                new_signals.clone_from(&signals);\n                for _ in 0..signals.len() {\n                    new_operators.push(AssignOp::AssignConstraintSignal);\n                }\n            }\n            if inputs.len() != new_signals.len() || inputs.len() != signals.len() {\n                return Err(AnonymousComponentError::boxed_report(&meta, \"The number of input arguments must be equal to the number of input signals of the template.\"));\n            }\n            for inp in inputs {\n                let mut acc = if var_access.is_none() {\n                    Vec::new()\n                } else {\n                    vec![build_array_access(var_access.as_ref().unwrap().clone())]\n                };\n                acc.push(Access::ComponentAccess(inp.0.clone()));\n                let (mut stmts, mut new_declarations, new_expr) = remove_anonymous_from_expression(\n                    templates,\n                    file_library,\n                    new_signals.get(i).unwrap().clone(),\n                    var_access,\n                )?;\n                if new_expr.contains_anonymous_component(None) {\n                    return Err(AnonymousComponentError::boxed_report(\n                        new_expr.meta(),\n                        \"The inputs to an anonymous component cannot contain anonymous components.\",\n                    ));\n                }\n                seq_substs.append(&mut stmts);\n                declarations.append(&mut new_declarations);\n                let subs = Statement::Substitution {\n                    meta: meta.clone(),\n                    var: id_anon_temp.clone(),\n                    access: acc,\n                    op: *new_operators.get(i).unwrap(),\n                    rhe: new_expr,\n                };\n                i += 1;\n                seq_substs.push(subs);\n            }\n            let outputs = template.unwrap().get_declaration_outputs();\n            if outputs.len() == 1 {\n                let output = outputs[0].0.clone();\n                let mut acc = if var_access.is_none() {\n                    Vec::new()\n                } else {\n                    vec![build_array_access(var_access.as_ref().unwrap().clone())]\n                };\n\n                acc.push(Access::ComponentAccess(output));\n                let out_exp =\n                    Expression::Variable { meta: meta.clone(), name: id_anon_temp, access: acc };\n                Ok((vec![Statement::Block { meta, stmts: seq_substs }], declarations, out_exp))\n            } else {\n                let mut new_values = Vec::new();\n                for output in outputs {\n                    let mut acc = if var_access.is_none() {\n                        Vec::new()\n                    } else {\n                        vec![build_array_access(var_access.as_ref().unwrap().clone())]\n                    };\n                    acc.push(Access::ComponentAccess(output.0.clone()));\n                    let out_exp = Expression::Variable {\n                        meta: meta.clone(),\n                        name: id_anon_temp.clone(),\n                        access: acc,\n                    };\n                    new_values.push(out_exp);\n                }\n                let out_exp = Tuple { meta: meta.clone(), values: new_values };\n                Ok((vec![Statement::Block { meta, stmts: seq_substs }], declarations, out_exp))\n            }\n        }\n        Tuple { meta, values } => {\n            let mut new_values = Vec::new();\n            let mut new_stmts: Vec<Statement> = Vec::new();\n            let mut declarations: Vec<Statement> = Vec::new();\n            for val in values {\n                let result =\n                    remove_anonymous_from_expression(templates, file_library, val, var_access);\n                match result {\n                    Ok((mut stm, mut declaration, val2)) => {\n                        new_stmts.append(&mut stm);\n                        new_values.push(val2);\n                        declarations.append(&mut declaration);\n                    }\n                    Err(er) => {\n                        return Err(er);\n                    }\n                }\n            }\n            Ok((new_stmts, declarations, build_tuple(meta, new_values)))\n        }\n        ParallelOp { meta, rhe } => {\n            if !rhe.is_call()\n                && !rhe.is_anonymous_component()\n                && rhe.contains_anonymous_component(None)\n            {\n                return Err(AnonymousComponentError::boxed_report(\n                    &meta,\n                    \"Invalid use of the parallel operator together with an anonymous component.\",\n                ));\n            } else if rhe.is_call() && rhe.contains_anonymous_component(None) {\n                return Err(AnonymousComponentError::boxed_report(\n                    &meta,\n                    \"An anonymous component cannot be used as a parameter in a template call.\",\n                ));\n            } else if rhe.is_anonymous_component() {\n                let rhe2 = rhe.make_anonymous_parallel();\n                return remove_anonymous_from_expression(templates, file_library, rhe2, var_access);\n            }\n            Ok((Vec::new(), Vec::new(), expr))\n        }\n    }\n}\n\nfn separate_declarations_in_comp_var_subs(\n    declarations: Vec<Statement>,\n) -> (Vec<Statement>, Vec<Statement>, Vec<Statement>) {\n    let mut components_dec = Vec::new();\n    let mut variables_dec = Vec::new();\n    let mut substitutions = Vec::new();\n    for dec in declarations {\n        if let Statement::Declaration { ref xtype, .. } = dec {\n            if matches!(xtype, VariableType::Component | VariableType::AnonymousComponent) {\n                components_dec.push(dec);\n            } else if VariableType::Var.eq(xtype) {\n                variables_dec.push(dec);\n            } else {\n                unreachable!();\n            }\n        } else if let Statement::Substitution { .. } = dec {\n            substitutions.push(dec);\n        } else {\n            unreachable!();\n        }\n    }\n    (components_dec, variables_dec, substitutions)\n}\n\nfn remove_tuples_from_statement(stmt: Statement) -> Result<Statement, Box<Report>> {\n    match stmt {\n        Statement::MultiSubstitution { meta, lhe, op, rhe } => {\n            let new_lhe = remove_tuple_from_expression(lhe)?;\n            let new_rhe = remove_tuple_from_expression(rhe)?;\n            match (new_lhe, new_rhe) {\n                (\n                    Expression::Tuple { values: mut lhe_values, .. },\n                    Expression::Tuple { values: mut rhe_values, .. },\n                ) => {\n                    if lhe_values.len() == rhe_values.len() {\n                        let mut substs = Vec::new();\n                        while !lhe_values.is_empty() {\n                            let lhe = lhe_values.remove(0);\n                            if let Expression::Variable { meta, name, access } = lhe {\n                                let rhe = rhe_values.remove(0);\n                                if name != \"_\" {\n                                    substs.push(build_substitution(\n                                        meta.clone(),\n                                        name.clone(),\n                                        access.to_vec(),\n                                        op,\n                                        rhe,\n                                    ));\n                                }\n                            } else {\n                                return Err(TupleError::boxed_report(&meta, \"The elements of the destination tuple must be either signals or variables.\"));\n                            }\n                        }\n                        Ok(build_block(meta, substs))\n                    } else if !lhe_values.is_empty() {\n                        Err(TupleError::boxed_report(\n                            &meta,\n                            \"The two tuples do not have the same length.\",\n                        ))\n                    } else {\n                        Err(TupleError::boxed_report(\n                            &meta,\n                            \"This expression must be the right-hand side of an assignment.\",\n                        ))\n                    }\n                }\n                (lhe, rhe) => {\n                    if lhe.is_tuple() || lhe.is_variable() {\n                        return Err(TupleError::boxed_report(\n                            rhe.meta(),\n                            \"This expression must be a tuple or an anonymous component.\",\n                        ));\n                    } else {\n                        return Err(TupleError::boxed_report(\n                            lhe.meta(),\n                            \"This expression must be a tuple, a component, a signal or a variable.\",\n                        ));\n                    }\n                }\n            }\n        }\n        Statement::IfThenElse { meta, cond, if_case, else_case } => {\n            if cond.contains_tuple(None) {\n                Err(TupleError::boxed_report(&meta, \"Tuples cannot be used in conditions.\"))\n            } else {\n                let new_if_case = remove_tuples_from_statement(*if_case)?;\n                match else_case {\n                    Some(else_case) => {\n                        let new_else_case = remove_tuples_from_statement(*else_case)?;\n                        Ok(Statement::IfThenElse {\n                            meta,\n                            cond,\n                            if_case: Box::new(new_if_case),\n                            else_case: Some(Box::new(new_else_case)),\n                        })\n                    }\n                    None => Ok(Statement::IfThenElse {\n                        meta,\n                        cond,\n                        if_case: Box::new(new_if_case),\n                        else_case: None,\n                    }),\n                }\n            }\n        }\n        Statement::While { meta, cond, stmt } => {\n            if cond.contains_tuple(None) {\n                Err(TupleError::boxed_report(&meta, \"Tuples cannot be used in conditions.\"))\n            } else {\n                let new_stmt = remove_tuples_from_statement(*stmt)?;\n                Ok(Statement::While { meta, cond, stmt: Box::new(new_stmt) })\n            }\n        }\n        Statement::LogCall { meta, args } => {\n            let mut new_args = Vec::new();\n            for arg in args {\n                match arg {\n                    LogArgument::LogStr(str) => {\n                        new_args.push(LogArgument::LogStr(str));\n                    }\n                    LogArgument::LogExp(exp) => {\n                        let mut sep_args = separate_tuple_for_log_call(vec![exp]);\n                        new_args.append(&mut sep_args);\n                    }\n                }\n            }\n            Ok(build_log_call(meta, new_args))\n        }\n        Statement::Assert { meta, arg } => Ok(build_assert(meta, arg)),\n        Statement::Return { meta, value } => {\n            if value.contains_tuple(None) {\n                Err(TupleError::boxed_report(&meta, \"Tuple cannot be used in return values.\"))\n            } else {\n                Ok(build_return(meta, value))\n            }\n        }\n        Statement::ConstraintEquality { meta, lhe, rhe } => {\n            if lhe.contains_tuple(None) || rhe.contains_tuple(None) {\n                Err(TupleError::boxed_report(\n                    &meta,\n                    \"Tuples cannot be used together with the constraint equality operator `===`.\",\n                ))\n            } else {\n                Ok(build_constraint_equality(meta, lhe, rhe))\n            }\n        }\n        Statement::Declaration { meta, xtype, name, dimensions, .. } => {\n            for expr in &dimensions {\n                if expr.contains_tuple(None) {\n                    return Err(TupleError::boxed_report(\n                        &meta,\n                        \"A tuple cannot be used to define the dimensions of an array.\",\n                    ));\n                }\n            }\n            Ok(build_declaration(meta, xtype, name, dimensions))\n        }\n        Statement::InitializationBlock { meta, xtype, initializations } => {\n            let mut new_inits = Vec::new();\n            for stmt in initializations {\n                let new_stmt = remove_tuples_from_statement(stmt)?;\n                new_inits.push(new_stmt);\n            }\n            Ok(Statement::InitializationBlock { meta, xtype, initializations: new_inits })\n        }\n        Statement::Block { meta, stmts } => {\n            let mut new_stmts = Vec::new();\n            for stmt in stmts {\n                let new_stmt = remove_tuples_from_statement(stmt)?;\n                new_stmts.push(new_stmt);\n            }\n            Ok(Statement::Block { meta, stmts: new_stmts })\n        }\n        Statement::Substitution { meta, var, op, rhe, access } => {\n            let new_rhe = remove_tuple_from_expression(rhe)?;\n            if new_rhe.is_tuple() {\n                return Err(TupleError::boxed_report(\n                    &meta,\n                    \"Left-hand side of the statement is not a tuple.\",\n                ));\n            }\n            for access in &access {\n                if let Access::ArrayAccess(index) = access {\n                    if index.contains_tuple(None) {\n                        return Err(TupleError::boxed_report(\n                            index.meta(),\n                            \"A tuple cannot be used to access an array.\",\n                        ));\n                    }\n                }\n            }\n            if var != \"_\" {\n                Ok(Statement::Substitution { meta, var, access, op, rhe: new_rhe })\n            } else {\n                // Since expressions cannot have side effects, we can ignore this.\n                Ok(build_block(meta, Vec::new()))\n            }\n        }\n    }\n}\n\nfn separate_tuple_for_log_call(values: Vec<Expression>) -> Vec<LogArgument> {\n    let mut new_values = Vec::new();\n    for value in values {\n        if let Expression::Tuple { values: values2, .. } = value {\n            new_values.push(LogArgument::LogStr(\"(\".to_string()));\n            let mut sep_values = separate_tuple_for_log_call(values2);\n            new_values.append(&mut sep_values);\n            new_values.push(LogArgument::LogStr(\")\".to_string()));\n        } else {\n            new_values.push(LogArgument::LogExp(value));\n        }\n    }\n    new_values\n}\n\nfn remove_tuple_from_expression(expr: Expression) -> Result<Expression, Box<Report>> {\n    use Expression::*;\n    match expr.clone() {\n        ArrayInLine { meta, values } => {\n            for value in values {\n                if value.contains_tuple(None) {\n                    return Err(TupleError::boxed_report(\n                        &meta,\n                        \"A tuple cannot be used to define the dimensions of an array.\",\n                    ));\n                }\n            }\n            Ok(expr)\n        }\n        Number(_, _) => Ok(expr),\n        Variable { meta, .. } => {\n            if expr.contains_tuple(None) {\n                return Err(TupleError::boxed_report(\n                    &meta,\n                    \"A tuple cannot be used to access an array.\",\n                ));\n            }\n            Ok(expr)\n        }\n        InfixOp { meta, lhe, rhe, .. } => {\n            if lhe.contains_tuple(None) || rhe.contains_tuple(None) {\n                return Err(TupleError::boxed_report(\n                    &meta,\n                    \"Tuples cannot be used in arithmetic or boolean expressions.\",\n                ));\n            }\n            Ok(expr)\n        }\n        PrefixOp { meta, rhe, .. } => {\n            if rhe.contains_tuple(None) {\n                return Err(TupleError::boxed_report(\n                    &meta,\n                    \"Tuples cannot be used in arithmetic or boolean expressions.\",\n                ));\n            }\n            Ok(expr)\n        }\n        InlineSwitchOp { meta, cond, if_true, if_false } => {\n            if cond.contains_tuple(None)\n                || if_true.contains_tuple(None)\n                || if_false.contains_tuple(None)\n            {\n                return Err(TupleError::boxed_report(\n                    &meta,\n                    \"Tuples cannot be used inside an inline switch expression.\",\n                ));\n            }\n            Ok(expr)\n        }\n        Call { meta, args, .. } => {\n            for value in args {\n                if value.contains_tuple(None) {\n                    return Err(TupleError::boxed_report(\n                        &meta,\n                        \"Tuples cannot be used as an argument to a function call.\",\n                    ));\n                }\n            }\n            Ok(expr)\n        }\n        AnonymousComponent { .. } => {\n            // This is called after anonymous components have been removed.\n            unreachable!();\n        }\n        Tuple { meta, values } => {\n            let mut unfolded_values = Vec::new();\n            for value in values {\n                let new_value = remove_tuple_from_expression(value)?;\n                if let Tuple { values: mut inner, .. } = new_value {\n                    unfolded_values.append(&mut inner);\n                } else {\n                    unfolded_values.push(new_value);\n                }\n            }\n            Ok(build_tuple(meta, unfolded_values))\n        }\n        ParallelOp { meta, rhe } => {\n            if rhe.contains_tuple(None) {\n                return Err(TupleError::boxed_report(\n                    &meta,\n                    \"Tuples cannot be used in parallel operators.\",\n                ));\n            }\n            Ok(expr)\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::parse_definition;\n\n    use super::*;\n\n    #[test]\n    fn test_desugar_multi_sub() {\n        let src = [\n            r#\"\n            template Anonymous(n) {\n                signal input a;\n                signal input b;\n                signal output c;\n                signal output d;\n                signal output e;\n\n                (c, d, e) <== (a + 1, b + 2, c + 3);\n            }\n        \"#,\n            r#\"\n            template Test(n) {\n                signal input a;\n                signal input b;\n                signal output c;\n                signal output d;\n\n                (c, _, d) <== Anonymous(n)(a, b);\n            }\n        \"#,\n        ];\n        validate_ast(&src, 0);\n    }\n\n    #[test]\n    fn test_nested_tuples() {\n        let src = [r#\"\n            template Test(n) {\n                signal input a;\n                signal input b;\n                signal output c;\n                signal output d;\n                signal output e;\n\n                ((c, d), (_)) <== ((a + 1, b + 2), (c + 3));\n            }\n        \"#];\n        validate_ast(&src, 0);\n\n        // TODO: Invalid, but is currently accepted by the compiler.\n        let src = [r#\"\n            template Test(n) {\n                signal input a;\n                signal input b;\n                signal output c;\n                signal output d;\n                signal output e;\n\n                ((c, d), e) <== (a + 1, (b + 2, c + 3));\n            }\n        \"#];\n        validate_ast(&src, 0);\n\n        // TODO: Invalid, but is currently accepted by the compiler.\n        let src = [r#\"\n            template Test(n) {\n                signal input a;\n                signal input b;\n                signal output c;\n\n                (((c))) <== (a + b);\n            }\n        \"#];\n        validate_ast(&src, 0);\n    }\n\n    #[test]\n    fn test_invalid_tuples() {\n        let src = [r#\"\n            template Test(n) {\n                signal input a;\n                signal input b;\n                signal output c;\n                signal output d;\n                signal output e;\n\n                ((c, d), e) <== (b + 2, c + 3);\n            }\n        \"#];\n        validate_ast(&src, 1);\n    }\n\n    fn validate_ast(src: &[&str], errors: usize) {\n        let mut reports = ReportCollection::new();\n        let (templates, file_library) = parse_templates(src);\n\n        // Verify that `remove_syntactic_sugar` is successful.\n        let (templates, _) =\n            remove_syntactic_sugar(&templates, &HashMap::new(), &file_library, &mut reports);\n        assert_eq!(reports.len(), errors);\n\n        // Ensure that no template contains a tuple or an anonymous component.\n        for template in templates.values() {\n            assert!(!template.get_body().contains_tuple(None));\n            assert!(!template.get_body().contains_anonymous_component(None));\n        }\n    }\n\n    fn parse_templates(src: &[&str]) -> (HashMap<String, TemplateData>, FileLibrary) {\n        let mut templates = HashMap::new();\n        let mut file_library = FileLibrary::new();\n        let mut elem_id = 0;\n        for src in src {\n            let file_id = file_library.add_file(\"memory\".to_string(), src.to_string(), true);\n            let definition = parse_definition(src).unwrap();\n            let Definition::Template {\n                name,\n                args,\n                arg_location,\n                body,\n                parallel,\n                is_custom_gate,\n                ..\n            } = definition\n            else {\n                unreachable!();\n            };\n            let template = TemplateData::new(\n                name.clone(),\n                file_id,\n                body,\n                args.len(),\n                args,\n                arg_location,\n                &mut elem_id,\n                parallel,\n                is_custom_gate,\n            );\n            templates.insert(name, template);\n        }\n        (templates, file_library)\n    }\n}\n"
  },
  {
    "path": "parser/src/syntax_sugar_traits.rs",
    "content": "use program_structure::ast::*;\nuse program_structure::report::ReportCollection;\n\nuse crate::errors::TupleError;\n\npub(crate) trait ContainsExpression {\n    /// Returns true if `self` contains `expr` such that `matcher(expr)`\n    /// evaluates to true. If the callback is not `None` it is invoked on\n    /// `expr.meta()` for each matching expression.\n    fn contains_expr(\n        &self,\n        matcher: &impl Fn(&Expression) -> bool,\n        callback: &mut impl FnMut(&Meta),\n    ) -> bool;\n\n    /// Returns true if the node contains a tuple. If `reports` is not `None`, a\n    /// report is generated for each occurrence.\n    fn contains_tuple(&self, reports: Option<&mut ReportCollection>) -> bool {\n        let matcher = |expr: &Expression| expr.is_tuple();\n        if let Some(reports) = reports {\n            let mut callback = |meta: &Meta| {\n                let error = TupleError::new(\n                    Some(meta),\n                    \"Tuples are not allowed in functions.\",\n                    Some(\"Tuple instantiated here.\"),\n                );\n                reports.push(error.into_report());\n            };\n            self.contains_expr(&matcher, &mut callback)\n        } else {\n            // We need to pass a dummy callback because rustc isn't smart enough\n            // to infer the type parameter to `Option` if we use options here.\n            let mut dummy = |_: &Meta| {};\n            self.contains_expr(&matcher, &mut dummy)\n        }\n    }\n\n    /// Returns true if the node contains an anonymous component. If `reports`\n    /// is not `None`, a report is generated for each occurrence.\n    fn contains_anonymous_component(&self, reports: Option<&mut ReportCollection>) -> bool {\n        let matcher = |expr: &Expression| expr.is_anonymous_component();\n        if let Some(reports) = reports {\n            let mut callback = |meta: &Meta| {\n                let error = TupleError::new(\n                    Some(meta),\n                    \"Anonymous components are not allowed in functions.\",\n                    Some(\"Anonymous component instantiated here.\"),\n                );\n                reports.push(error.into_report());\n            };\n            self.contains_expr(&matcher, &mut callback)\n        } else {\n            // We need to pass a dummy callback because rustc isn't smart enough\n            // to infer the type parameter to `Option` if we use options here.\n            let mut dummy = |_: &Meta| {};\n            self.contains_expr(&matcher, &mut dummy)\n        }\n    }\n}\n\nimpl ContainsExpression for Expression {\n    fn contains_expr(\n        &self,\n        matcher: &impl Fn(&Expression) -> bool,\n        callback: &mut impl FnMut(&Meta),\n    ) -> bool {\n        use Expression::*;\n        // Check if the current expression matches and invoke the callback if\n        // defined.\n        if matcher(self) {\n            callback(self.meta());\n            return true;\n        }\n        let mut result = false;\n        match &self {\n            InfixOp { lhe, rhe, .. } => {\n                result = lhe.contains_expr(matcher, callback) || result;\n                result = rhe.contains_expr(matcher, callback) || result;\n                result\n            }\n            PrefixOp { rhe, .. } => rhe.contains_expr(matcher, callback),\n            InlineSwitchOp { cond, if_true, if_false, .. } => {\n                result = cond.contains_expr(matcher, callback) || result;\n                result = if_true.contains_expr(matcher, callback) || result;\n                result = if_false.contains_expr(matcher, callback) || result;\n                result\n            }\n            Call { args, .. } => {\n                for arg in args {\n                    result = arg.contains_expr(matcher, callback) || result;\n                }\n                result\n            }\n            ArrayInLine { values, .. } => {\n                for value in values {\n                    result = value.contains_expr(matcher, callback) || result;\n                }\n                result\n            }\n            AnonymousComponent { params, signals, .. } => {\n                for param in params {\n                    result = param.contains_expr(matcher, callback) || result;\n                }\n                for signal in signals {\n                    result = signal.contains_expr(matcher, callback) || result;\n                }\n                result\n            }\n            Variable { access, .. } => {\n                for access in access {\n                    if let Access::ArrayAccess(index) = access {\n                        result = index.contains_expr(matcher, callback) || result;\n                    }\n                }\n                result\n            }\n            Number(_, _) => false,\n            Tuple { values, .. } => {\n                for value in values {\n                    result = value.contains_expr(matcher, callback) || result;\n                }\n                result\n            }\n            ParallelOp { rhe, .. } => rhe.contains_expr(matcher, callback),\n        }\n    }\n}\n\nimpl ContainsExpression for Statement {\n    fn contains_expr(\n        &self,\n        matcher: &impl Fn(&Expression) -> bool,\n        callback: &mut impl FnMut(&Meta),\n    ) -> bool {\n        use LogArgument::*;\n        use Statement::*;\n        use Access::*;\n        let mut result = false;\n        match self {\n            IfThenElse { cond, if_case, else_case, .. } => {\n                result = cond.contains_expr(matcher, callback) || result;\n                result = if_case.contains_expr(matcher, callback) || result;\n                if let Some(else_case) = else_case {\n                    result = else_case.contains_expr(matcher, callback) || result;\n                }\n                result\n            }\n            While { cond, stmt, .. } => {\n                result = cond.contains_expr(matcher, callback) || result;\n                result = stmt.contains_expr(matcher, callback) || result;\n                result\n            }\n            Return { value, .. } => value.contains_expr(matcher, callback),\n            InitializationBlock { initializations, .. } => {\n                for init in initializations {\n                    result = init.contains_expr(matcher, callback) || result;\n                }\n                result\n            }\n            Block { stmts, .. } => {\n                for stmt in stmts {\n                    result = stmt.contains_expr(matcher, callback) || result;\n                }\n                result\n            }\n            Declaration { dimensions, .. } => {\n                for size in dimensions {\n                    result = size.contains_expr(matcher, callback) || result;\n                }\n                result\n            }\n            Substitution { access, rhe, .. } => {\n                for access in access {\n                    if let ArrayAccess(index) = access {\n                        result = index.contains_expr(matcher, callback) || result;\n                    }\n                }\n                result = rhe.contains_expr(matcher, callback) || result;\n                result\n            }\n            MultiSubstitution { lhe, rhe, .. } => {\n                result = lhe.contains_expr(matcher, callback) || result;\n                result = rhe.contains_expr(matcher, callback) || result;\n                result\n            }\n            ConstraintEquality { lhe, rhe, .. } => {\n                result = lhe.contains_expr(matcher, callback) || result;\n                result = rhe.contains_expr(matcher, callback) || result;\n                result\n            }\n            LogCall { args, .. } => {\n                for arg in args {\n                    if let LogExp(expr) = arg {\n                        result = expr.contains_expr(matcher, callback) || result;\n                    }\n                }\n                result\n            }\n            Assert { arg, .. } => arg.contains_expr(matcher, callback),\n        }\n    }\n}\n"
  },
  {
    "path": "program_analysis/Cargo.toml",
    "content": "[package]\nname = \"circomspect-program-analysis\"\nversion = \"0.8.2\"\nedition = \"2021\"\nrust-version = \"1.65\"\nlicense = \"LGPL-3.0-only\"\nauthors = [\"Trail of Bits\"]\ndescription = \"Support crate for the Circomspect static analyzer\"\nrepository = \"https://github.com/trailofbits/circomspect\"\n\n[dependencies]\nanyhow = \"1.0\"\nlog = \"0.4\"\nnum-bigint-dig = \"0.8\"\nnum-traits = \"0.2\"\nthiserror = \"1.0\"\nparser = { package = \"circomspect-parser\", version = \"2.2.0\", path = \"../parser\" }\nprogram_structure = { package = \"circomspect-program-structure\", version = \"2.1.4\", path = \"../program_structure\" }\n\n[dev-dependencies]\nparser = { package = \"circomspect-parser\", version = \"2.2.0\", path = \"../parser\" }\nprogram_structure = { package = \"circomspect-program-structure\", version = \"2.1.4\", path = \"../program_structure\" }\n"
  },
  {
    "path": "program_analysis/src/analysis_context.rs",
    "content": "use thiserror::Error;\n\nuse program_structure::{\n    file_definition::{FileID, FileLocation},\n    cfg::Cfg,\n};\n\n/// Errors returned by the analysis context.\n#[derive(Debug, Error)]\npub enum AnalysisError {\n    /// This function has no corresponding AST.\n    #[error(\"Unknown function `{name}`.\")]\n    UnknownFunction { name: String },\n    /// This template has no corresponding AST.\n    #[error(\"Unknown template `{name}`.\")]\n    UnknownTemplate { name: String },\n    /// This function has an AST, but we failed to lift it to a corresponding\n    /// CFG.\n    #[error(\"Failed to lift the function `{name}`.\")]\n    FailedToLiftFunction { name: String },\n    /// This template has an AST, but we failed to lift it to a corresponding\n    /// CFG.\n    #[error(\"Failed to lift the template `{name}`.\")]\n    FailedToLiftTemplate { name: String },\n    /// The file ID does not correspond to a known file.\n    #[error(\"Unknown file ID `{file_id}`.\")]\n    UnknownFile { file_id: FileID },\n    /// The location does not exist in the file with the given ID.\n    #[error(\"The location `{}:{}` is not valid for the file with file ID `{file_id}`.\", file_location.start, file_location.end)]\n    InvalidLocation { file_id: FileID, file_location: FileLocation },\n}\n\n/// Context passed to each analysis pass.\npub trait AnalysisContext {\n    /// Returns true if the context knows of a function with the given name.\n    /// This method does not compute the CFG of the function which saves time\n    /// compared to `AnalysisContext::function`.\n    fn is_function(&self, name: &str) -> bool;\n\n    /// Returns true if the context knows of a template with the given name.\n    /// This method does not compute the CFG of the template which saves time\n    /// compared to `AnalysisContext::template`.\n    fn is_template(&self, name: &str) -> bool;\n\n    /// Returns the CFG for the function with the given name.\n    fn function(&mut self, name: &str) -> Result<&Cfg, AnalysisError>;\n\n    /// Returns the CFG for the template with the given name.\n    fn template(&mut self, name: &str) -> Result<&Cfg, AnalysisError>;\n\n    /// Returns the string corresponding to the given file ID and location.\n    fn underlying_str(\n        &self,\n        file_id: &FileID,\n        file_location: &FileLocation,\n    ) -> Result<String, AnalysisError>;\n}\n"
  },
  {
    "path": "program_analysis/src/analysis_runner.rs",
    "content": "use log::{debug, trace};\nuse std::path::PathBuf;\nuse std::collections::HashMap;\n\nuse parser::ParseResult;\n\nuse program_structure::{\n    writers::{LogWriter, ReportWriter},\n    template_data::TemplateInfo,\n    function_data::FunctionInfo,\n    file_definition::{FileLibrary, FileLocation, FileID},\n    cfg::{Cfg, IntoCfg},\n    constants::Curve,\n    report::{ReportCollection, Report},\n};\n\n#[cfg(test)]\nuse program_structure::template_library::TemplateLibrary;\n\nuse crate::{\n    analysis_context::{AnalysisContext, AnalysisError},\n    get_analysis_passes, config,\n};\n\ntype CfgCache = HashMap<String, Cfg>;\ntype ReportCache = HashMap<String, ReportCollection>;\n\n/// A type responsible for caching CFGs and running analysis passes over all\n/// functions and templates.\n#[derive(Default)]\npub struct AnalysisRunner {\n    curve: Curve,\n    libraries: Vec<PathBuf>,\n    /// The corresponding file library including file includes.\n    file_library: FileLibrary,\n    /// Template ASTs generated by the parser.\n    template_asts: TemplateInfo,\n    /// Function ASTs generated by the parser.\n    function_asts: FunctionInfo,\n    /// Cached template CFGs generated on demand.\n    template_cfgs: CfgCache,\n    /// Cached function CFGs generated on demand.\n    function_cfgs: CfgCache,\n    /// Reports created during CFG generation.\n    template_reports: ReportCache,\n    /// Reports created during CFG generation.\n    function_reports: ReportCache,\n}\n\nimpl AnalysisRunner {\n    pub fn new(curve: Curve) -> Self {\n        AnalysisRunner { curve, ..Default::default() }\n    }\n\n    pub fn with_libraries(mut self, libraries: &[PathBuf]) -> Self {\n        self.libraries.extend_from_slice(libraries);\n        self\n    }\n\n    pub fn with_files(mut self, input_files: &[PathBuf]) -> (Self, ReportCollection) {\n        let reports =\n            match parser::parse_files(input_files, &self.libraries, &config::COMPILER_VERSION) {\n                ParseResult::Program(program, warnings) => {\n                    self.template_asts = program.templates;\n                    self.function_asts = program.functions;\n                    self.file_library = program.file_library;\n                    warnings\n                }\n                ParseResult::Library(library, warnings) => {\n                    self.template_asts = library.templates;\n                    self.function_asts = library.functions;\n                    self.file_library = library.file_library;\n                    warnings\n                }\n            };\n        (self, reports)\n    }\n\n    /// Convenience method used to generate a runner for testing purposes.\n    #[cfg(test)]\n    pub fn with_src(mut self, file_contents: &[&str]) -> Self {\n        use parser::parse_definition;\n\n        let mut library_contents = HashMap::new();\n        let mut file_library = FileLibrary::default();\n        for (file_index, file_source) in file_contents.iter().enumerate() {\n            let file_name = format!(\"file-{file_index}.circom\");\n            let file_id = file_library.add_file(file_name, file_source.to_string(), true);\n            library_contents.insert(file_id, vec![parse_definition(file_source).unwrap()]);\n        }\n        let template_library = TemplateLibrary::new(library_contents, file_library.clone());\n        self.template_asts = template_library.templates;\n        self.function_asts = template_library.functions;\n        self.file_library = template_library.file_library;\n\n        self\n    }\n\n    pub fn file_library(&self) -> &FileLibrary {\n        &self.file_library\n    }\n\n    pub fn template_names(&self, user_input_only: bool) -> Vec<String> {\n        // Clone template names to avoid holding multiple references to `self`.\n        self.template_asts\n            .iter()\n            .filter_map(|(name, ast)| {\n                if !user_input_only || self.file_library.is_user_input(ast.get_file_id()) {\n                    Some(name)\n                } else {\n                    None\n                }\n            })\n            .cloned()\n            .collect()\n    }\n\n    pub fn function_names(&self, user_input_only: bool) -> Vec<String> {\n        // Clone function names to avoid holding multiple references to `self`.\n        self.function_asts\n            .iter()\n            .filter_map(|(name, ast)| {\n                if !user_input_only || self.file_library.is_user_input(ast.get_file_id()) {\n                    Some(name)\n                } else {\n                    None\n                }\n            })\n            .cloned()\n            .collect()\n    }\n\n    fn analyze_template<W: LogWriter + ReportWriter>(&mut self, name: &str, writer: &mut W) {\n        writer.write_message(&format!(\"analyzing template '{name}'\"));\n\n        // We take ownership of the CFG and any previously generated reports\n        // here to avoid holding multiple mutable and immutable references to\n        // `self`. This may lead to the CFG being regenerated during analysis if\n        // the template is invoked recursively. If it is then ¯\\_(ツ)_/¯.\n        let mut reports = self.take_template_reports(name);\n        if let Ok(cfg) = self.take_template(name) {\n            for analysis_pass in get_analysis_passes() {\n                reports.append(&mut analysis_pass(self, &cfg));\n            }\n            // Re-insert the CFG into the hash map.\n            if self.replace_template(name, cfg) {\n                debug!(\"template `{name}` CFG was regenerated during analysis\");\n            }\n        }\n        writer.write_reports(&reports, &self.file_library);\n    }\n\n    pub fn analyze_templates<W: LogWriter + ReportWriter>(\n        &mut self,\n        writer: &mut W,\n        user_input_only: bool,\n    ) {\n        for name in self.template_names(user_input_only) {\n            self.analyze_template(&name, writer);\n        }\n    }\n\n    fn analyze_function<W: LogWriter + ReportWriter>(&mut self, name: &str, writer: &mut W) {\n        writer.write_message(&format!(\"analyzing function '{name}'\"));\n\n        // We take ownership of the CFG and any previously generated reports\n        // here to avoid holding multiple mutable and immutable references to\n        // `self`. This may lead to the CFG being regenerated during analysis if\n        // the function is invoked recursively. If it is then ¯\\_(ツ)_/¯.\n        let mut reports = self.take_function_reports(name);\n        if let Ok(cfg) = self.take_function(name) {\n            for analysis_pass in get_analysis_passes() {\n                reports.append(&mut analysis_pass(self, &cfg));\n            }\n            // Re-insert the CFG into the hash map.\n            if self.replace_function(name, cfg) {\n                debug!(\"function `{name}` CFG was regenerated during analysis\");\n            }\n        }\n        writer.write_reports(&reports, &self.file_library);\n    }\n\n    pub fn analyze_functions<W: LogWriter + ReportWriter>(\n        &mut self,\n        writer: &mut W,\n        user_input_only: bool,\n    ) {\n        for name in self.function_names(user_input_only) {\n            self.analyze_function(&name, writer);\n        }\n    }\n\n    /// Report cache from CFG generation. These will be emitted when the\n    /// template is analyzed.\n    fn append_template_reports(&mut self, name: &str, reports: &mut ReportCollection) {\n        self.template_reports.entry(name.to_string()).or_default().append(reports);\n    }\n\n    /// Report cache from CFG generation. These will be emitted when the\n    /// template is analyzed.\n    fn take_template_reports(&mut self, name: &str) -> ReportCollection {\n        self.template_reports.remove(name).unwrap_or_default()\n    }\n\n    /// Report cache from CFG generation. These will be emitted when the\n    /// function is analyzed.\n    fn append_function_reports(&mut self, name: &str, reports: &mut ReportCollection) {\n        self.function_reports.entry(name.to_string()).or_default().append(reports);\n    }\n\n    /// Report cache from CFG generation. These will be emitted when the\n    /// function is analyzed.\n    fn take_function_reports(&mut self, name: &str) -> ReportCollection {\n        self.function_reports.remove(name).unwrap_or_default()\n    }\n\n    fn cache_template(&mut self, name: &str) -> Result<&Cfg, AnalysisError> {\n        if !self.template_cfgs.contains_key(name) {\n            // The template CFG needs to be generated from the AST.\n            if self.template_reports.contains_key(name) {\n                // We have already failed to generate the CFG.\n                return Err(AnalysisError::FailedToLiftTemplate { name: name.to_string() });\n            }\n            // Get the AST corresponding to the template.\n            let Some(ast) = self.template_asts.get(name) else {\n                trace!(\"failed to lift unknown template `{name}`\");\n                return Err(AnalysisError::UnknownTemplate { name: name.to_string() });\n            };\n            // Generate the template CFG from the AST. Cache any reports.\n            let mut reports = ReportCollection::new();\n            let cfg = generate_cfg(ast, &self.curve, &mut reports).map_err(|report| {\n                reports.push(*report);\n                trace!(\"failed to lift template `{name}`\");\n                AnalysisError::FailedToLiftTemplate { name: name.to_string() }\n            })?;\n            self.append_template_reports(name, &mut reports);\n            self.template_cfgs.insert(name.to_string(), cfg);\n            trace!(\"successfully lifted template `{name}`\");\n        }\n        Ok(self.template_cfgs.get(name).unwrap())\n    }\n\n    fn cache_function(&mut self, name: &str) -> Result<&Cfg, AnalysisError> {\n        if !self.function_cfgs.contains_key(name) {\n            // The function CFG needs to be generated from the AST.\n            if self.function_reports.contains_key(name) {\n                // We have already failed to generate the CFG.\n                return Err(AnalysisError::FailedToLiftFunction { name: name.to_string() });\n            }\n            // Get the AST corresponding to the function.\n            let Some(ast) = self.function_asts.get(name) else {\n                trace!(\"failed to lift unknown function `{name}`\");\n                return Err(AnalysisError::UnknownFunction { name: name.to_string() });\n            };\n            // Generate the function CFG from the AST. Cache any reports.\n            let mut reports = ReportCollection::new();\n            let cfg = generate_cfg(ast, &self.curve, &mut reports).map_err(|report| {\n                reports.push(*report);\n                trace!(\"failed to lift function `{name}`\");\n                AnalysisError::FailedToLiftFunction { name: name.to_string() }\n            })?;\n            self.append_function_reports(name, &mut reports);\n            self.function_cfgs.insert(name.to_string(), cfg);\n            trace!(\"successfully lifted function `{name}`\");\n        }\n        Ok(self.function_cfgs.get(name).unwrap())\n    }\n\n    pub fn take_template(&mut self, name: &str) -> Result<Cfg, AnalysisError> {\n        self.cache_template(name)?;\n        // The CFG must be available since caching was successful.\n        Ok(self.template_cfgs.remove(name).unwrap())\n    }\n\n    pub fn take_function(&mut self, name: &str) -> Result<Cfg, AnalysisError> {\n        self.cache_function(name)?;\n        // The CFG must be available since caching was successful.\n        Ok(self.function_cfgs.remove(name).unwrap())\n    }\n\n    pub fn replace_template(&mut self, name: &str, cfg: Cfg) -> bool {\n        self.template_cfgs.insert(name.to_string(), cfg).is_some()\n    }\n\n    pub fn replace_function(&mut self, name: &str, cfg: Cfg) -> bool {\n        self.function_cfgs.insert(name.to_string(), cfg).is_some()\n    }\n}\n\nimpl AnalysisContext for AnalysisRunner {\n    fn is_template(&self, name: &str) -> bool {\n        self.template_asts.contains_key(name)\n    }\n\n    fn is_function(&self, name: &str) -> bool {\n        self.function_asts.contains_key(name)\n    }\n\n    fn template(&mut self, name: &str) -> Result<&Cfg, AnalysisError> {\n        self.cache_template(name)\n    }\n\n    fn function(&mut self, name: &str) -> Result<&Cfg, AnalysisError> {\n        self.cache_function(name)\n    }\n\n    fn underlying_str(\n        &self,\n        file_id: &FileID,\n        file_location: &FileLocation,\n    ) -> Result<String, AnalysisError> {\n        let Ok(file) = self.file_library.to_storage().get(*file_id) else {\n            return Err(AnalysisError::UnknownFile { file_id: *file_id });\n        };\n        if file_location.end <= file.source().len() {\n            Ok(file.source()[file_location.start..file_location.end].to_string())\n        } else {\n            Err(AnalysisError::InvalidLocation {\n                file_id: *file_id,\n                file_location: file_location.clone(),\n            })\n        }\n    }\n}\n\nfn generate_cfg<Ast: IntoCfg>(\n    ast: Ast,\n    curve: &Curve,\n    reports: &mut ReportCollection,\n) -> Result<Cfg, Box<Report>> {\n    ast.into_cfg(curve, reports)\n        .map_err(|error| Box::new(error.into()))?\n        .into_ssa()\n        .map_err(|error| Box::new(error.into()))\n}\n\n#[cfg(test)]\nmod tests {\n    use program_structure::ir::Statement;\n\n    use super::*;\n\n    #[test]\n    fn test_function() {\n        let mut runner = AnalysisRunner::new(Curve::Goldilocks).with_src(&[r#\"\n            function foo(a) {\n                return a[0] + a[1];\n            }\n        \"#]);\n\n        // Check that `foo` is a known function, that we can access the CFG\n        // for `foo`, and that the CFG is properly cached.\n        assert!(runner.is_function(\"foo\"));\n        assert!(!runner.function_cfgs.contains_key(\"foo\"));\n        assert!(runner.function(\"foo\").is_ok());\n        assert!(runner.function_cfgs.contains_key(\"foo\"));\n\n        // Check that the `take_function` and `replace_function` APIs work as expected.\n        let cfg = runner.take_function(\"foo\").unwrap();\n        assert!(!runner.function_cfgs.contains_key(\"foo\"));\n        assert!(!runner.replace_function(\"foo\", cfg));\n        assert!(runner.function_cfgs.contains_key(\"foo\"));\n\n        // Check that `baz` is not a known function, that attempting to access\n        // `baz` produces an error, and that nothing is cached.\n        assert!(!runner.is_function(\"baz\"));\n        assert!(!runner.function_cfgs.contains_key(\"baz\"));\n        assert!(matches!(runner.function(\"baz\"), Err(AnalysisError::UnknownFunction { .. })));\n        assert!(!runner.function_cfgs.contains_key(\"baz\"));\n    }\n\n    #[test]\n    fn test_template() {\n        let mut runner = AnalysisRunner::new(Curve::Goldilocks).with_src(&[r#\"\n            template Foo(n) {\n                signal input a[2];\n\n                a[0] === a[1];\n            }\n        \"#]);\n\n        // Check that `Foo` is a known template, that we can access the CFG\n        // for `Foo`, and that the CFG is properly cached.\n        assert!(runner.is_template(\"Foo\"));\n        assert!(!runner.template_cfgs.contains_key(\"Foo\"));\n        assert!(runner.template(\"Foo\").is_ok());\n        assert!(runner.template_cfgs.contains_key(\"Foo\"));\n\n        // Check that the `take_template` and `replace_template` APIs work as expected.\n        let cfg = runner.take_template(\"Foo\").unwrap();\n        assert!(!runner.template_cfgs.contains_key(\"Foo\"));\n        assert!(!runner.replace_template(\"Foo\", cfg));\n        assert!(runner.template_cfgs.contains_key(\"Foo\"));\n\n        // Check that `Baz` is not a known template, that attempting to access\n        // `Baz` produces an error, and that nothing is cached.\n        assert!(!runner.is_template(\"Baz\"));\n        assert!(!runner.template_cfgs.contains_key(\"Baz\"));\n        assert!(matches!(runner.template(\"Baz\"), Err(AnalysisError::UnknownTemplate { .. })));\n        assert!(!runner.template_cfgs.contains_key(\"Baz\"));\n    }\n\n    #[test]\n    fn test_underlying_str() {\n        use Statement::*;\n        let mut runner = AnalysisRunner::new(Curve::Goldilocks).with_src(&[r#\"\n            template Foo(n) {\n                signal input a[2];\n\n                a[0] === a[1];\n            }\n        \"#]);\n\n        let cfg = runner.take_template(\"Foo\").unwrap();\n        for stmt in cfg.entry_block().iter() {\n            let file_id = stmt.meta().file_id().unwrap();\n            let file_location = stmt.meta().file_location();\n            let string = runner.underlying_str(&file_id, &file_location).unwrap();\n            match stmt {\n                // TODO: Why do some statements include the semi-colon and others don't?\n                Declaration { .. } => assert_eq!(string, \"signal input a[2]\"),\n                ConstraintEquality { .. } => assert_eq!(string, \"a[0] === a[1];\"),\n                _ => unreachable!(),\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "program_analysis/src/bitwise_complement.rs",
    "content": "use log::debug;\n\nuse program_structure::cfg::Cfg;\nuse program_structure::report_code::ReportCode;\nuse program_structure::report::{Report, ReportCollection};\nuse program_structure::file_definition::{FileID, FileLocation};\nuse program_structure::ir::*;\n\npub struct BitwiseComplementWarning {\n    file_id: Option<FileID>,\n    file_location: FileLocation,\n}\n\nimpl BitwiseComplementWarning {\n    pub fn into_report(self) -> Report {\n        let mut report = Report::info(\n            \"The bitwise complement is reduced modulo `p`, which means that `(~x)ᵢ != ~(xᵢ)` in general.\".to_string(),\n            ReportCode::FieldElementArithmetic,\n        );\n        if let Some(file_id) = self.file_id {\n            report.add_primary(\n                self.file_location,\n                file_id,\n                \"256-bit complement taken here.\".to_string(),\n            );\n        }\n        report\n    }\n}\n\n/// The output of `~x` is reduced modulo `p`, which means that individual bits\n/// will typically not satisfy the expected relation `(~x)ᵢ != ~(xᵢ)`. This may\n/// lead to unexpected results if the developer is not careful.\npub fn find_bitwise_complement(cfg: &Cfg) -> ReportCollection {\n    debug!(\"running bitwise complement analysis pass\");\n    let mut reports = ReportCollection::new();\n    for basic_block in cfg.iter() {\n        for stmt in basic_block.iter() {\n            visit_statement(stmt, &mut reports);\n        }\n    }\n    debug!(\"{} new reports generated\", reports.len());\n    reports\n}\n\nfn visit_statement(stmt: &Statement, reports: &mut ReportCollection) {\n    use Statement::*;\n    match stmt {\n        Declaration { dimensions, .. } => {\n            for size in dimensions {\n                visit_expression(size, reports);\n            }\n        }\n        LogCall { args, .. } => {\n            use LogArgument::*;\n            for arg in args {\n                if let Expr(value) = arg {\n                    visit_expression(value, reports);\n                }\n            }\n        }\n        IfThenElse { cond, .. } => visit_expression(cond, reports),\n        Substitution { rhe, .. } => visit_expression(rhe, reports),\n        Return { value, .. } => visit_expression(value, reports),\n        Assert { arg, .. } => visit_expression(arg, reports),\n        ConstraintEquality { lhe, rhe, .. } => {\n            visit_expression(lhe, reports);\n            visit_expression(rhe, reports);\n        }\n    }\n}\n\nfn visit_expression(expr: &Expression, reports: &mut ReportCollection) {\n    use Expression::*;\n    use ExpressionPrefixOpcode::*;\n    match expr {\n        PrefixOp { meta, prefix_op: Complement, .. } => {\n            reports.push(build_report(meta));\n        }\n        PrefixOp { rhe, .. } => {\n            visit_expression(rhe, reports);\n        }\n        InfixOp { lhe, rhe, .. } => {\n            visit_expression(lhe, reports);\n            visit_expression(rhe, reports);\n        }\n        SwitchOp { cond, if_true, if_false, .. } => {\n            visit_expression(cond, reports);\n            visit_expression(if_true, reports);\n            visit_expression(if_false, reports);\n        }\n        Call { args, .. } => {\n            for arg in args {\n                visit_expression(arg, reports);\n            }\n        }\n        InlineArray { values, .. } => {\n            for value in values {\n                visit_expression(value, reports);\n            }\n        }\n        Access { access, .. } => {\n            for access in access {\n                if let AccessType::ArrayAccess(index) = access {\n                    visit_expression(index, reports);\n                }\n            }\n        }\n        Update { access, rhe, .. } => {\n            visit_expression(rhe, reports);\n            for access in access {\n                if let AccessType::ArrayAccess(index) = access {\n                    visit_expression(index, reports);\n                }\n            }\n        }\n        Variable { .. } | Number(_, _) | Phi { .. } => (),\n    }\n}\n\nfn build_report(meta: &Meta) -> Report {\n    BitwiseComplementWarning { file_id: meta.file_id(), file_location: meta.file_location() }\n        .into_report()\n}\n\n#[cfg(test)]\nmod tests {\n    use parser::parse_definition;\n    use program_structure::{cfg::IntoCfg, constants::Curve};\n\n    use super::*;\n\n    #[test]\n    fn test_bitwise_complement() {\n        let src = r#\"\n            function f() {\n                return (1 > 2)? 3: ~4;\n            }\n        \"#;\n        validate_reports(src, 1);\n\n        let src = r#\"\n            function f() {\n                return (1 > 2)? 3: 4;\n            }\n        \"#;\n        validate_reports(src, 0);\n    }\n\n    fn validate_reports(src: &str, expected_len: usize) {\n        // Build CFG.\n        let mut reports = ReportCollection::new();\n        let cfg = parse_definition(src)\n            .unwrap()\n            .into_cfg(&Curve::default(), &mut reports)\n            .unwrap()\n            .into_ssa()\n            .unwrap();\n        assert!(reports.is_empty());\n\n        // Generate report collection.\n        let reports = find_bitwise_complement(&cfg);\n\n        assert_eq!(reports.len(), expected_len);\n    }\n}\n"
  },
  {
    "path": "program_analysis/src/bn254_specific_circuit.rs",
    "content": "use std::collections::HashSet;\n\nuse log::debug;\n\nuse program_structure::cfg::Cfg;\nuse program_structure::constants::Curve;\nuse program_structure::ir::{AssignOp, Expression, Meta, Statement};\nuse program_structure::report::{Report, ReportCollection};\nuse program_structure::report_code::ReportCode;\nuse program_structure::file_definition::{FileLocation, FileID};\n\nconst PROBLEMATIC_GOLDILOCK_TEMPLATES: [&str; 26] = [\n    \"BabyPbk\",\n    \"AliasCheck\",\n    \"CompConstant\",\n    \"Num2Bits_strict\",\n    \"Bits2Num_strict\",\n    \"EdDSAVerifier\",\n    \"EdDSAMiMCVerifier\",\n    \"EdDSAMiMCSpongeVerifier\",\n    \"EdDSAPoseidonVerifier\",\n    \"EscalarMulAny\",\n    \"MiMC7\",\n    \"MultiMiMC7\",\n    \"MiMCFeistel\",\n    \"MiMCSponge\",\n    \"Pedersen\",\n    \"Bits2Point_Strict\",\n    \"Point2Bits_Strict\",\n    \"PoseidonEx\",\n    \"Poseidon\",\n    \"Sign\",\n    \"SMTHash1\",\n    \"SMTHash2\",\n    \"SMTProcessor\",\n    \"SMTProcessorLevel\",\n    \"SMTVerifier\",\n    \"SMTVerifierLevel\",\n];\n\nconst PROBLEMATIC_BLS12_381_TEMPLATES: [&str; 13] = [\n    \"AliasCheck\",\n    \"CompConstant\",\n    \"Num2Bits_strict\",\n    \"Bits2Num_strict\",\n    \"EdDSAVerifier\",\n    \"EdDSAMiMCVerifier\",\n    \"EdDSAMiMCSpongeVerifier\",\n    \"EdDSAPoseidonVerifier\",\n    \"Bits2Point_Strict\",\n    \"Point2Bits_Strict\",\n    \"SMTVerifier\",\n    \"SMTProcessor\",\n    \"Sign\",\n];\n\npub struct Bn254SpecificCircuitWarning {\n    template_name: String,\n    file_id: Option<FileID>,\n    file_location: FileLocation,\n}\n\nimpl Bn254SpecificCircuitWarning {\n    pub fn into_report(self) -> Report {\n        let mut report = Report::warning(\n            format!(\n                \"The `{}` template relies on BN254 specific parameters and should not be used with other curves.\",\n                self.template_name\n            ),\n            ReportCode::Bn254SpecificCircuit,\n        );\n        if let Some(file_id) = self.file_id {\n            report.add_primary(\n                self.file_location,\n                file_id,\n                format!(\"`{}` instantiated here.\", self.template_name),\n            );\n        }\n        report\n    }\n}\n\n// This analysis pass identifies Circomlib templates with hard-coded constants\n// related to BN254. If these are used together with a different prime, this may\n// be an issue.\n//\n// The following table contains a check for each problematic template-curve pair.\n//\n// Template             Goldilocks (64 bits)        BLS12-381 (255 bits)\n// -----------------------------------------------------------------\n// AliasCheck                   x                           x\n// BabyPbk                      x\n// Bits2Num_strict              x                           x\n// Num2Bits_strict              x                           x\n// CompConstant                 x                           x\n// EdDSAVerifier                x                           x\n// EdDSAMiMCVerifier            x                           x\n// EdDSAMiMCSpongeVerifier      x                           x\n// EdDSAPoseidonVerifier        x                           x\n// EscalarMulAny                x\n// MiMC7                        x\n// MultiMiMC7                   x\n// MiMCFeistel                  x\n// MiMCSponge                   x\n// Pedersen                     x\n// Bits2Point_strict            x                           x\n// Point2Bits_strict            x                           x\n// PoseidonEx                   x\n// Poseidon                     x\n// Sign                         x                           x\n// SMTHash1                     x\n// SMTHash2                     x\n// SMTProcessor                 x                           x\n// SMTProcessorLevel            x\n// SMTVerifier                  x                           x\n// SMTVerifierLevel             x\npub fn find_bn254_specific_circuits(cfg: &Cfg) -> ReportCollection {\n    let problematic_templates = match cfg.constants().curve() {\n        Curve::Goldilocks => HashSet::from(PROBLEMATIC_GOLDILOCK_TEMPLATES),\n        Curve::Bls12_381 => HashSet::from(PROBLEMATIC_BLS12_381_TEMPLATES),\n        Curve::Bn254 => {\n            // Exit early if we're using the default curve.\n            return ReportCollection::new();\n        }\n    };\n    debug!(\"running bn254-specific circuit analysis pass\");\n    let mut reports = ReportCollection::new();\n    for basic_block in cfg.iter() {\n        for stmt in basic_block.iter() {\n            visit_statement(stmt, &problematic_templates, &mut reports);\n        }\n    }\n    debug!(\"{} new reports generated\", reports.len());\n    reports\n}\n\nfn visit_statement(\n    stmt: &Statement,\n    problematic_templates: &HashSet<&str>,\n    reports: &mut ReportCollection,\n) {\n    use AssignOp::*;\n    use Expression::*;\n    use Statement::*;\n    if let Substitution { meta: var_meta, op: AssignLocalOrComponent, rhe, .. } = stmt {\n        // If the variable `var` is declared as a local variable or signal, we exit early.\n        if var_meta.type_knowledge().is_local() || var_meta.type_knowledge().is_signal() {\n            return;\n        }\n        // If this is an update node, we extract the right-hand side.\n        let rhe = if let Update { rhe, .. } = rhe { rhe } else { rhe };\n\n        // A component initialization on the form `var = component_name(...)`.\n        if let Call { meta: component_meta, name: component_name, .. } = rhe {\n            if problematic_templates.contains(&&component_name[..]) {\n                reports.push(build_report(component_meta, component_name));\n            }\n        }\n    }\n}\n\nfn build_report(meta: &Meta, name: &str) -> Report {\n    Bn254SpecificCircuitWarning {\n        template_name: name.to_string(),\n        file_id: meta.file_id,\n        file_location: meta.file_location(),\n    }\n    .into_report()\n}\n\n#[cfg(test)]\nmod tests {\n    use parser::parse_definition;\n    use program_structure::{cfg::IntoCfg, constants::Curve};\n\n    use super::*;\n\n    #[test]\n    fn test_num2bits_strict() {\n        let src = r#\"\n            template T(n) {\n                signal input in;\n                signal output out[n];\n\n                component n2b = Num2Bits_strict(n);\n                n2b.in === in;\n                for (var i = 0; i < n; i++) {\n                    out[i] <== n2b.out[i];\n                }\n            }\n        \"#;\n        validate_reports(src, 1);\n\n        let src = r#\"\n            template T(n) {\n                signal input in;\n                signal output out[n];\n\n                component n2b = Num2Bits(n);\n                n2b.in === in;\n                for (var i = 0; i < n; i++) {\n                    out[i] <== n2b.out[i];\n                }\n            }\n        \"#;\n        validate_reports(src, 0);\n    }\n\n    fn validate_reports(src: &str, expected_len: usize) {\n        // Build CFG.\n        let mut reports = ReportCollection::new();\n        let cfg = parse_definition(src)\n            .unwrap()\n            .into_cfg(&Curve::Bls12_381, &mut reports)\n            .unwrap()\n            .into_ssa()\n            .unwrap();\n        assert!(reports.is_empty());\n\n        // Generate report collection.\n        let reports = find_bn254_specific_circuits(&cfg);\n\n        assert_eq!(reports.len(), expected_len);\n    }\n}\n"
  },
  {
    "path": "program_analysis/src/config.rs",
    "content": "use program_structure::ast::Version;\n\npub const COMPILER_VERSION: Version = (2, 1, 4);\npub const DEFAULT_LEVEL: &str = \"WARNING\";\npub const DEFAULT_CURVE: &str = \"BN254\";\n"
  },
  {
    "path": "program_analysis/src/constant_conditional.rs",
    "content": "use log::debug;\n\nuse program_structure::cfg::Cfg;\nuse program_structure::report_code::ReportCode;\nuse program_structure::report::{Report, ReportCollection};\nuse program_structure::file_definition::{FileID, FileLocation};\nuse program_structure::ir::value_meta::ValueReduction;\nuse program_structure::ir::*;\n\npub struct ConstantBranchConditionWarning {\n    value: bool,\n    file_id: Option<FileID>,\n    file_location: FileLocation,\n}\n\nimpl ConstantBranchConditionWarning {\n    pub fn into_report(self) -> Report {\n        let mut report = Report::warning(\n            \"Constant branching statement condition found.\".to_string(),\n            ReportCode::ConstantBranchCondition,\n        );\n        if let Some(file_id) = self.file_id {\n            report.add_primary(\n                self.file_location,\n                file_id,\n                format!(\"This condition is always {}.\", self.value),\n            );\n        }\n        report\n    }\n}\n\n/// This analysis pass uses basic constant propagation to determine cases where\n/// an if-statement condition is always true or false.\npub fn find_constant_conditional_statement(cfg: &Cfg) -> ReportCollection {\n    debug!(\"running constant conditional analysis pass\");\n    let mut reports = ReportCollection::new();\n    for basic_block in cfg.iter() {\n        for stmt in basic_block.iter() {\n            visit_statement(stmt, &mut reports);\n        }\n    }\n    debug!(\"{} new reports generated\", reports.len());\n    reports\n}\n\nfn visit_statement(stmt: &Statement, reports: &mut ReportCollection) {\n    use Statement::*;\n    use ValueReduction::*;\n    if let IfThenElse { cond, .. } = stmt {\n        let value = cond.meta().value_knowledge().get_reduces_to();\n        if let Some(Boolean { value }) = value {\n            reports.push(build_report(cond.meta(), *value));\n        }\n    }\n}\n\nfn build_report(meta: &Meta, value: bool) -> Report {\n    ConstantBranchConditionWarning {\n        value,\n        file_id: meta.file_id(),\n        file_location: meta.file_location(),\n    }\n    .into_report()\n}\n\n#[cfg(test)]\nmod tests {\n    use parser::parse_definition;\n    use program_structure::{cfg::IntoCfg, constants::Curve};\n\n    use super::*;\n\n    #[test]\n    fn test_constant_conditional() {\n        let src = r#\"\n            function f(x) {\n                var a = 1;\n                var b = (2 * a * a + 1) << 2;\n                var c = (3 * b / b - 2) >> 1;\n                if (c > 4) {\n                    a += x;\n                    b += x * a;\n                }\n                return a + b;\n            }\n        \"#;\n        validate_reports(src, 1);\n\n        let src = r#\"\n            function f(x) {\n                var a = 1;\n                var b = (2 * a * a + 1) << 2;\n                var c = (3 * b / x - 2) >> 1;\n                if (c > 4) {\n                    a += x;\n                    b += x * a;\n                }\n                return a + b;\n            }\n        \"#;\n        validate_reports(src, 0);\n    }\n\n    fn validate_reports(src: &str, expected_len: usize) {\n        // Build CFG.\n        let mut reports = ReportCollection::new();\n        let cfg = parse_definition(src)\n            .unwrap()\n            .into_cfg(&Curve::default(), &mut reports)\n            .unwrap()\n            .into_ssa()\n            .unwrap();\n        assert!(reports.is_empty());\n\n        // Generate report collection.\n        let reports = find_constant_conditional_statement(&cfg);\n\n        assert_eq!(reports.len(), expected_len);\n    }\n}\n"
  },
  {
    "path": "program_analysis/src/constraint_analysis.rs",
    "content": "use log::{debug, trace};\nuse std::collections::{HashMap, HashSet};\n\nuse program_structure::cfg::Cfg;\nuse program_structure::intermediate_representation::variable_meta::VariableMeta;\nuse program_structure::intermediate_representation::AssignOp;\nuse program_structure::ir::variable_meta::VariableUse;\nuse program_structure::ir::{Statement, VariableName};\n\n/// This analysis computes the transitive closure of the constraint relation.\n/// (Note that the resulting relation will be symmetric, but not reflexive in\n/// general.)\n#[derive(Clone, Default)]\npub struct ConstraintAnalysis {\n    constraint_map: HashMap<VariableName, HashSet<VariableName>>,\n    declarations: HashMap<VariableName, VariableUse>,\n    definitions: HashMap<VariableName, VariableUse>,\n}\n\nimpl ConstraintAnalysis {\n    fn new() -> ConstraintAnalysis {\n        ConstraintAnalysis::default()\n    }\n\n    /// Add the variable use corresponding to the definition of the variable.\n    fn add_definition(&mut self, var: &VariableUse) {\n        // TODO: Since we don't version components and signals, we may end up\n        // overwriting component initializations here. For example, in the\n        // following case the component initialization will be clobbered.\n        //\n        //   component c[2];\n        //   ...\n        //   c[0].in[0] <== 0;\n        //   c[1].in[1] <== 1;\n        //\n        // The constraint map should probably track VariableAccesses rather\n        // than VariableNames.\n        self.definitions.insert(var.name().clone(), var.clone());\n    }\n\n    /// Get the variable use corresponding to the definition of the variable.\n    pub fn get_definition(&self, var: &VariableName) -> Option<VariableUse> {\n        self.definitions.get(var).cloned()\n    }\n\n    pub fn definitions(&self) -> impl Iterator<Item = &VariableUse> {\n        self.definitions.values()\n    }\n\n    /// Add the variable use corresponding to the declaration of the variable.\n    fn add_declaration(&mut self, var: &VariableUse) {\n        self.declarations.insert(var.name().clone(), var.clone());\n    }\n\n    /// Get the variable use corresponding to the declaration of the variable.\n    pub fn get_declaration(&self, var: &VariableName) -> Option<VariableUse> {\n        self.declarations.get(var).cloned()\n    }\n\n    pub fn declarations(&self) -> impl Iterator<Item = &VariableUse> {\n        self.declarations.values()\n    }\n\n    /// Add a constraint from source to sink.\n    fn add_constraint_step(&mut self, source: &VariableName, sink: &VariableName) {\n        let sinks = self.constraint_map.entry(source.clone()).or_default();\n        sinks.insert(sink.clone());\n    }\n\n    /// Returns variables constrained in a single step by `source`.\n    pub fn single_step_constraint(&self, source: &VariableName) -> HashSet<VariableName> {\n        self.constraint_map.get(source).cloned().unwrap_or_default()\n    }\n\n    /// Returns variables constrained in one or more steps by `source`.\n    pub fn multi_step_constraint(&self, source: &VariableName) -> HashSet<VariableName> {\n        let mut result = HashSet::new();\n        let mut update = self.single_step_constraint(source);\n        while !update.is_subset(&result) {\n            result.extend(update.iter().cloned());\n            update = update.iter().flat_map(|source| self.single_step_constraint(source)).collect();\n        }\n        result\n    }\n\n    /// Returns true if the source constrains any of the sinks.\n    pub fn constrains_any(&self, source: &VariableName, sinks: &HashSet<VariableName>) -> bool {\n        self.multi_step_constraint(source).iter().any(|sink| sinks.contains(sink))\n    }\n\n    /// Returns the set of variables occurring in a constraint together with at\n    /// least one other variable.\n    pub fn constrained_variables(&self) -> HashSet<VariableName> {\n        self.constraint_map.keys().cloned().collect::<HashSet<_>>()\n    }\n}\n\npub fn run_constraint_analysis(cfg: &Cfg) -> ConstraintAnalysis {\n    debug!(\"running constraint analysis pass\");\n    let mut result = ConstraintAnalysis::new();\n\n    use AssignOp::*;\n    use Statement::*;\n    for basic_block in cfg.iter() {\n        for stmt in basic_block.iter() {\n            trace!(\"visiting statement `{stmt:?}`\");\n            // Add definitions to the result.\n            for var in stmt.variables_written() {\n                result.add_definition(var);\n            }\n            match stmt {\n                Declaration { meta, names, .. } => {\n                    // Add declarations to the result.\n                    for sink in names {\n                        result.add_declaration(&VariableUse::new(meta, sink, &Vec::new()));\n                    }\n                }\n                ConstraintEquality { .. } | Substitution { op: AssignConstraintSignal, .. } => {\n                    for source in stmt.variables_used() {\n                        for sink in stmt.variables_used() {\n                            if source.name() != sink.name() {\n                                trace!(\n                                    \"adding constraint step with source `{:?}` and sink `{:?}`\",\n                                    source.name(),\n                                    sink.name()\n                                );\n                                result.add_constraint_step(source.name(), sink.name());\n                            }\n                        }\n                    }\n                }\n                _ => {}\n            }\n        }\n    }\n    result\n}\n\n#[cfg(test)]\nmod tests {\n    use parser::parse_definition;\n    use program_structure::cfg::IntoCfg;\n    use program_structure::constants::Curve;\n    use program_structure::report::ReportCollection;\n\n    use super::*;\n\n    #[test]\n    fn test_single_step_constraint() {\n        let src = r#\"\n            template T(n) {\n                signal input in;\n                signal output out;\n                signal tmp;\n\n                tmp <== 2 * in;\n                out <== in * in;\n\n            }\n        \"#;\n        let sources = [\n            VariableName::from_string(\"in\"),\n            VariableName::from_string(\"out\"),\n            VariableName::from_string(\"tmp\"),\n        ];\n        let sinks = [2, 1, 1];\n        validate_constraints(src, &sources, &sinks);\n\n        let src = r#\"\n            template T(n) {\n                signal input in;\n                signal output out;\n                signal tmp;\n\n                tmp === 2 * in;\n                out <== in * in;\n\n            }\n        \"#;\n        let sources = [\n            VariableName::from_string(\"in\"),\n            VariableName::from_string(\"out\"),\n            VariableName::from_string(\"tmp\"),\n        ];\n        let sinks = [2, 1, 1];\n        validate_constraints(src, &sources, &sinks);\n    }\n\n    fn validate_constraints(src: &str, sources: &[VariableName], sinks: &[usize]) {\n        // Build CFG.\n        let mut reports = ReportCollection::new();\n        let cfg = parse_definition(src)\n            .unwrap()\n            .into_cfg(&Curve::default(), &mut reports)\n            .unwrap()\n            .into_ssa()\n            .unwrap();\n        assert!(reports.is_empty());\n\n        // Run constraint analysis.\n        let constraint_analysis = run_constraint_analysis(&cfg);\n        for (source, sinks) in sources.iter().zip(sinks) {\n            assert_eq!(constraint_analysis.single_step_constraint(source).len(), *sinks)\n        }\n    }\n}\n"
  },
  {
    "path": "program_analysis/src/definition_complexity.rs",
    "content": "use program_structure::cfg::{Cfg, DefinitionType};\nuse program_structure::report_code::ReportCode;\nuse program_structure::report::{Report, ReportCollection};\nuse program_structure::file_definition::{FileID, FileLocation};\n\npub struct TooManyArgumentsWarning {\n    definition_name: String,\n    definition_type: DefinitionType,\n    file_id: Option<FileID>,\n    file_location: FileLocation,\n}\n\nimpl TooManyArgumentsWarning {\n    pub fn into_report(self) -> Report {\n        let mut report = Report::warning(\n            format!(\n                \"`{}` takes too many parameters. This increases coupling and decreases readability.\",\n                self.definition_name\n            ),\n            ReportCode::TooManyArguments,\n        );\n        if let Some(file_id) = self.file_id {\n            report.add_primary(\n                self.file_location,\n                file_id,\n                format!(\"This {} takes too many parameters.\", self.definition_type),\n            );\n        }\n        report\n    }\n}\n\npub struct CyclomaticComplexityWarning {\n    definition_name: String,\n    definition_type: DefinitionType,\n}\n\nimpl CyclomaticComplexityWarning {\n    pub fn into_report(self) -> Report {\n        Report::warning(\n            format!(\n                \"The {} `{}` is too complex and would benefit from being refactored into smaller components.\",\n                self.definition_type,\n                self.definition_name\n            ),\n            ReportCode::CyclomaticComplexity,\n        )\n    }\n}\n\nconst MAX_NOF_PARAMETERS: usize = 7;\nconst MAX_CYCLOMATIC_COMPLEXITY: usize = 20;\n\npub fn run_complexity_analysis(cfg: &Cfg) -> ReportCollection {\n    // Compute the cyclomatic complexity as `M = E - N + 2P` where `E` is the\n    // number of edges, `N` is the number of nodes, and `P` is the number of\n    // connected components (which is always 1 here).\n    let mut edges = 0;\n    let mut nodes = 0;\n    for basic_block in cfg.iter() {\n        edges += basic_block.successors().len();\n        nodes += 1;\n    }\n    let complexity = 2 + edges - nodes;\n\n    let mut reports = ReportCollection::new();\n    // Generate a report if the cyclomatic complexity is high.\n    if complexity > MAX_CYCLOMATIC_COMPLEXITY {\n        reports.push(\n            CyclomaticComplexityWarning {\n                definition_name: cfg.name().to_string(),\n                definition_type: cfg.definition_type().clone(),\n            }\n            .into_report(),\n        );\n    }\n    // Generate a report if the number of arguments is high.\n    if cfg.parameters().len() > MAX_NOF_PARAMETERS {\n        reports.push(\n            TooManyArgumentsWarning {\n                definition_name: cfg.name().to_string(),\n                definition_type: cfg.definition_type().clone(),\n                file_id: *cfg.parameters().file_id(),\n                file_location: cfg.parameters().file_location().clone(),\n            }\n            .into_report(),\n        );\n    }\n    reports\n}\n\n#[cfg(test)]\nmod tests {\n    use parser::parse_definition;\n    use program_structure::{report::ReportCollection, constants::Curve, cfg::IntoCfg};\n\n    use crate::definition_complexity::run_complexity_analysis;\n\n    #[test]\n    fn test_small_template() {\n        let src = r#\"\n            template Example () {\n               signal input a;\n               signal output b;\n               a <== b;\n            }\n        \"#;\n        validate_reports(src, 0);\n    }\n\n    fn validate_reports(src: &str, expected_len: usize) {\n        // Build CFG.\n        let mut reports = ReportCollection::new();\n        let cfg = parse_definition(src)\n            .unwrap()\n            .into_cfg(&Curve::default(), &mut reports)\n            .unwrap()\n            .into_ssa()\n            .unwrap();\n        assert!(reports.is_empty());\n\n        // Generate report collection.\n        let reports = run_complexity_analysis(&cfg);\n\n        assert_eq!(reports.len(), expected_len);\n    }\n}\n"
  },
  {
    "path": "program_analysis/src/field_arithmetic.rs",
    "content": "use log::debug;\n\nuse program_structure::cfg::Cfg;\nuse program_structure::report_code::ReportCode;\nuse program_structure::report::{Report, ReportCollection};\nuse program_structure::file_definition::{FileID, FileLocation};\nuse program_structure::ir::*;\n\npub struct FieldElementArithmeticWarning {\n    file_id: Option<FileID>,\n    file_location: FileLocation,\n}\n\nimpl FieldElementArithmeticWarning {\n    pub fn into_report(self) -> Report {\n        let mut report = Report::info(\n            \"Field element arithmetic could overflow, which may produce unexpected results.\"\n                .to_string(),\n            ReportCode::FieldElementArithmetic,\n        );\n        if let Some(file_id) = self.file_id {\n            report.add_primary(\n                self.file_location,\n                file_id,\n                \"Field element arithmetic here.\".to_string(),\n            );\n        }\n        report\n    }\n}\n\n/// Field element arithmetic in Circom may overflow, which could produce\n/// unexpected results. Worst case, it may allow a malicious prover to forge\n/// proofs.\npub fn find_field_element_arithmetic(cfg: &Cfg) -> ReportCollection {\n    debug!(\"running field element arithmetic analysis pass\");\n    let mut reports = ReportCollection::new();\n    for basic_block in cfg.iter() {\n        for stmt in basic_block.iter() {\n            visit_statement(stmt, &mut reports);\n        }\n    }\n    debug!(\"{} new reports generated\", reports.len());\n    reports\n}\n\nfn visit_statement(stmt: &Statement, reports: &mut ReportCollection) {\n    use Statement::*;\n    match stmt {\n        Declaration { dimensions, .. } => {\n            for size in dimensions {\n                visit_expression(size, reports);\n            }\n        }\n        LogCall { args, .. } => {\n            use LogArgument::*;\n            for arg in args {\n                if let Expr(value) = arg {\n                    visit_expression(value, reports);\n                }\n            }\n        }\n        IfThenElse { cond, .. } => visit_expression(cond, reports),\n        Substitution { rhe, .. } => visit_expression(rhe, reports),\n        Return { value, .. } => visit_expression(value, reports),\n        Assert { arg, .. } => visit_expression(arg, reports),\n        ConstraintEquality { lhe, rhe, .. } => {\n            visit_expression(lhe, reports);\n            visit_expression(rhe, reports);\n        }\n    }\n}\n\nfn visit_expression(expr: &Expression, reports: &mut ReportCollection) {\n    use Expression::*;\n    match expr {\n        InfixOp { meta, infix_op, .. } if may_overflow(infix_op) => {\n            reports.push(build_report(meta));\n        }\n        InfixOp { lhe, rhe, .. } => {\n            visit_expression(lhe, reports);\n            visit_expression(rhe, reports);\n        }\n        PrefixOp { rhe, .. } => {\n            visit_expression(rhe, reports);\n        }\n        SwitchOp { cond, if_true, if_false, .. } => {\n            visit_expression(cond, reports);\n            visit_expression(if_true, reports);\n            visit_expression(if_false, reports);\n        }\n        Call { args, .. } => {\n            for arg in args {\n                visit_expression(arg, reports);\n            }\n        }\n        InlineArray { values, .. } => {\n            for value in values {\n                visit_expression(value, reports);\n            }\n        }\n        Access { access, .. } => {\n            for index in access {\n                if let AccessType::ArrayAccess(index) = index {\n                    visit_expression(index, reports);\n                }\n            }\n        }\n        Update { access, rhe, .. } => {\n            for index in access {\n                if let AccessType::ArrayAccess(index) = index {\n                    visit_expression(index, reports);\n                }\n            }\n            visit_expression(rhe, reports);\n        }\n        Number(_, _) | Variable { .. } | Phi { .. } => (),\n    }\n}\n\nfn is_arithmetic_infix_op(op: &ExpressionInfixOpcode) -> bool {\n    use ExpressionInfixOpcode::*;\n    matches!(\n        op,\n        Mul | Div | Add | Sub | Pow | IntDiv | Mod | ShiftL | ShiftR | BitOr | BitAnd | BitXor\n    )\n}\n\nfn may_overflow(op: &ExpressionInfixOpcode) -> bool {\n    use ExpressionInfixOpcode::*;\n    // Note that right-shift may overflow if the shift is less than 0.\n    is_arithmetic_infix_op(op) && !matches!(op, IntDiv | Mod | BitOr | BitAnd | BitXor)\n}\n\nfn build_report(meta: &Meta) -> Report {\n    FieldElementArithmeticWarning { file_id: meta.file_id(), file_location: meta.file_location() }\n        .into_report()\n}\n\n#[cfg(test)]\nmod tests {\n    use parser::parse_definition;\n    use program_structure::{cfg::IntoCfg, constants::Curve};\n\n    use super::*;\n\n    #[test]\n    fn test_field_arithmetic() {\n        let src = r#\"\n            function f(a) {\n                var b[2] = [0, 1];\n                var c = b[a + 1];\n                return a + b[1] + c;\n            }\n        \"#;\n        validate_reports(src, 2);\n    }\n\n    fn validate_reports(src: &str, expected_len: usize) {\n        // Build CFG.\n        let mut reports = ReportCollection::new();\n        let cfg = parse_definition(src)\n            .unwrap()\n            .into_cfg(&Curve::default(), &mut reports)\n            .unwrap()\n            .into_ssa()\n            .unwrap();\n        assert!(reports.is_empty());\n\n        // Generate report collection.\n        let reports = find_field_element_arithmetic(&cfg);\n\n        assert_eq!(reports.len(), expected_len);\n    }\n}\n"
  },
  {
    "path": "program_analysis/src/field_comparisons.rs",
    "content": "use log::debug;\n\nuse program_structure::cfg::Cfg;\nuse program_structure::report_code::ReportCode;\nuse program_structure::report::{Report, ReportCollection};\nuse program_structure::file_definition::{FileID, FileLocation};\nuse program_structure::ir::*;\n\npub struct FieldElementComparisonWarning {\n    file_id: Option<FileID>,\n    file_location: FileLocation,\n}\n\nimpl FieldElementComparisonWarning {\n    pub fn into_report(self) -> Report {\n        let mut report = Report::info(\n            \"Comparisons with field elements greater than `p/2` may produce unexpected results.\"\n                .to_string(),\n            ReportCode::FieldElementComparison,\n        );\n        if let Some(file_id) = self.file_id {\n            report.add_primary(\n                self.file_location,\n                file_id,\n                \"Field element comparison here.\".to_string(),\n            );\n        }\n        report.add_note(\n            \"Field elements are always normalized to the interval `(-p/2, p/2]` before they are compared.\".to_string()\n        );\n        report\n    }\n}\n\n/// Field element comparisons in Circom may produce surprising results since\n/// elements are normalized to the the half-open interval `(-p/2, p/2]` before\n/// they are compared. In particular, this means that the statements\n///\n///   1. `p/2 + 1 < 0`,\n///   2. `p/2 + 1 < p/2 - 1`, and\n///   3. `2 * x < x` for any `p/4 < x < p/2`\n///\n/// are all true.\npub fn find_field_element_comparisons(cfg: &Cfg) -> ReportCollection {\n    debug!(\"running field element comparison analysis pass\");\n    let mut reports = ReportCollection::new();\n    for basic_block in cfg.iter() {\n        for stmt in basic_block.iter() {\n            visit_statement(stmt, &mut reports);\n        }\n    }\n    debug!(\"{} new reports generated\", reports.len());\n    reports\n}\n\nfn visit_statement(stmt: &Statement, reports: &mut ReportCollection) {\n    use Statement::*;\n    match stmt {\n        Declaration { dimensions, .. } => {\n            for size in dimensions {\n                visit_expression(size, reports);\n            }\n        }\n        LogCall { args, .. } => {\n            use LogArgument::*;\n            for arg in args {\n                if let Expr(value) = arg {\n                    visit_expression(value, reports);\n                }\n            }\n        }\n        IfThenElse { cond, .. } => visit_expression(cond, reports),\n        Substitution { rhe, .. } => visit_expression(rhe, reports),\n        Return { value, .. } => visit_expression(value, reports),\n        Assert { arg, .. } => visit_expression(arg, reports),\n        ConstraintEquality { lhe, rhe, .. } => {\n            visit_expression(lhe, reports);\n            visit_expression(rhe, reports);\n        }\n    }\n}\n\nfn visit_expression(expr: &Expression, reports: &mut ReportCollection) {\n    use Expression::*;\n    match expr {\n        InfixOp { meta, infix_op, .. } if is_comparison_op(infix_op) => {\n            reports.push(build_report(meta));\n        }\n        InfixOp { lhe, rhe, .. } => {\n            visit_expression(lhe, reports);\n            visit_expression(rhe, reports);\n        }\n        PrefixOp { rhe, .. } => {\n            visit_expression(rhe, reports);\n        }\n        SwitchOp { cond, if_true, if_false, .. } => {\n            visit_expression(cond, reports);\n            visit_expression(if_true, reports);\n            visit_expression(if_false, reports);\n        }\n        Call { args, .. } => {\n            for arg in args {\n                visit_expression(arg, reports);\n            }\n        }\n        InlineArray { values, .. } => {\n            for value in values {\n                visit_expression(value, reports);\n            }\n        }\n        Access { access, .. } => {\n            for index in access {\n                if let AccessType::ArrayAccess(index) = index {\n                    visit_expression(index, reports);\n                }\n            }\n        }\n        Update { access, rhe, .. } => {\n            for index in access {\n                if let AccessType::ArrayAccess(index) = index {\n                    visit_expression(index, reports);\n                }\n            }\n            visit_expression(rhe, reports);\n        }\n        Number(_, _) | Variable { .. } | Phi { .. } => (),\n    }\n}\n\nfn is_comparison_op(op: &ExpressionInfixOpcode) -> bool {\n    use ExpressionInfixOpcode::*;\n    matches!(op, LesserEq | GreaterEq | Lesser | Greater)\n}\n\nfn build_report(meta: &Meta) -> Report {\n    FieldElementComparisonWarning { file_id: meta.file_id(), file_location: meta.file_location() }\n        .into_report()\n}\n\n#[cfg(test)]\nmod tests {\n    use parser::parse_definition;\n    use program_structure::{cfg::IntoCfg, constants::Curve};\n\n    use super::*;\n\n    #[test]\n    fn test_field_comparisons() {\n        let src = r#\"\n            function f(a) {\n                var b = a + 1;\n                while (a > 0) {\n                    a -= 1;\n                }\n                if (b < a + 2) {\n                    a += 1;\n                }\n                var c = a + b + 1;\n                return (a < b) && (b < c);\n            }\n        \"#;\n        validate_reports(src, 4);\n    }\n\n    fn validate_reports(src: &str, expected_len: usize) {\n        // Build CFG.\n        let mut reports = ReportCollection::new();\n        let cfg = parse_definition(src)\n            .unwrap()\n            .into_cfg(&Curve::default(), &mut reports)\n            .unwrap()\n            .into_ssa()\n            .unwrap();\n        assert!(reports.is_empty());\n\n        // Generate report collection.\n        let reports = find_field_element_comparisons(&cfg);\n\n        assert_eq!(reports.len(), expected_len);\n    }\n}\n"
  },
  {
    "path": "program_analysis/src/lib.rs",
    "content": "use analysis_context::AnalysisContext;\n\nuse program_structure::cfg::Cfg;\nuse program_structure::report::ReportCollection;\n\nextern crate num_bigint_dig as num_bigint;\n\npub mod constraint_analysis;\npub mod taint_analysis;\npub mod analysis_context;\npub mod analysis_runner;\npub mod config;\n\n// Intra-process analysis passes.\nmod bitwise_complement;\nmod bn254_specific_circuit;\nmod constant_conditional;\nmod definition_complexity;\nmod field_arithmetic;\nmod field_comparisons;\nmod nonstrict_binary_conversion;\nmod under_constrained_signals;\nmod unconstrained_less_than;\nmod unconstrained_division;\nmod side_effect_analysis;\nmod signal_assignments;\n\n// Inter-process analysis passes.\nmod unused_output_signal;\n\n/// An analysis pass is a function which takes an analysis context and a CFG and\n/// returns a set of reports.\ntype AnalysisPass = dyn Fn(&mut dyn AnalysisContext, &Cfg) -> ReportCollection;\n\npub fn get_analysis_passes() -> Vec<Box<AnalysisPass>> {\n    vec![\n        // Intra-process analysis passes.\n        Box::new(|_, cfg| bitwise_complement::find_bitwise_complement(cfg)),\n        Box::new(|_, cfg| signal_assignments::find_signal_assignments(cfg)),\n        Box::new(|_, cfg| definition_complexity::run_complexity_analysis(cfg)),\n        Box::new(|_, cfg| side_effect_analysis::run_side_effect_analysis(cfg)),\n        Box::new(|_, cfg| field_arithmetic::find_field_element_arithmetic(cfg)),\n        Box::new(|_, cfg| field_comparisons::find_field_element_comparisons(cfg)),\n        Box::new(|_, cfg| unconstrained_division::find_unconstrained_division(cfg)),\n        Box::new(|_, cfg| bn254_specific_circuit::find_bn254_specific_circuits(cfg)),\n        Box::new(|_, cfg| unconstrained_less_than::find_unconstrained_less_than(cfg)),\n        Box::new(|_, cfg| constant_conditional::find_constant_conditional_statement(cfg)),\n        Box::new(|_, cfg| under_constrained_signals::find_under_constrained_signals(cfg)),\n        Box::new(|_, cfg| nonstrict_binary_conversion::find_nonstrict_binary_conversion(cfg)),\n        // Inter-process analysis passes.\n        Box::new(unused_output_signal::find_unused_output_signals),\n    ]\n}\n"
  },
  {
    "path": "program_analysis/src/nonstrict_binary_conversion.rs",
    "content": "use log::debug;\nuse num_bigint::BigInt;\n\nuse program_structure::cfg::{Cfg, DefinitionType};\nuse program_structure::constants::Curve;\nuse program_structure::report_code::ReportCode;\nuse program_structure::report::{Report, ReportCollection};\nuse program_structure::file_definition::{FileID, FileLocation};\nuse program_structure::ir::value_meta::{ValueMeta, ValueReduction};\nuse program_structure::ir::*;\n\npub enum NonStrictBinaryConversionWarning {\n    Num2Bits { file_id: Option<FileID>, location: FileLocation },\n    Bits2Num { file_id: Option<FileID>, location: FileLocation },\n}\n\nimpl NonStrictBinaryConversionWarning {\n    pub fn into_report(self) -> Report {\n        match self {\n            NonStrictBinaryConversionWarning::Num2Bits { file_id, location } => {\n                let mut report = Report::warning(\n                    \"Using `Num2Bits` to convert field elements to bits may lead to aliasing issues.\".to_string(),\n                    ReportCode::NonStrictBinaryConversion,\n                );\n                if let Some(file_id) = file_id {\n                    report.add_primary(\n                        location,\n                        file_id,\n                        \"Circomlib template `Num2Bits` instantiated here.\".to_string(),\n                    );\n                }\n                report.add_note(\n                    \"Consider using `Num2Bits_strict` if the input size may be >= than the prime size.\"\n                        .to_string(),\n                );\n                report\n            }\n            NonStrictBinaryConversionWarning::Bits2Num { file_id, location } => {\n                let mut report = Report::warning(\n                    \"Using `Bits2Num` to convert arrays to field elements may lead to aliasing issues.\".to_string(),\n                    ReportCode::NonStrictBinaryConversion,\n                );\n                if let Some(file_id) = file_id {\n                    report.add_primary(\n                        location,\n                        file_id,\n                        \"Circomlib template `Bits2Num` instantiated here.\".to_string(),\n                    );\n                }\n                report.add_note(\n                    \"Consider using `Bits2Num_strict` if the input size may be >= than the prime size.\"\n                        .to_string(),\n                );\n                report\n            }\n        }\n    }\n}\n\n/// If the size in bits of the input `x` to the Circomlib circuit `NumBits` is\n/// >= than the size of the prime there will be two valid bit-representations of\n/// the input: One representation of `x` and one of `p + x`. This is typically\n/// not expected by developers and may lead to issues.\npub fn find_nonstrict_binary_conversion(cfg: &Cfg) -> ReportCollection {\n    use DefinitionType::*;\n    if matches!(cfg.definition_type(), Function | CustomTemplate) {\n        // Exit early if this is a function or custom template.\n        return ReportCollection::new();\n    }\n    if cfg.constants().curve() != &Curve::Bn254 {\n        // Exit early if we're not using the default curve.\n        return ReportCollection::new();\n    }\n    debug!(\"running non-strict `Num2Bits` analysis pass\");\n    let mut reports = ReportCollection::new();\n    let prime_size = BigInt::from(cfg.constants().prime_size());\n    for basic_block in cfg.iter() {\n        for stmt in basic_block.iter() {\n            visit_statement(stmt, &prime_size, &mut reports);\n        }\n    }\n    debug!(\"{} new reports generated\", reports.len());\n    reports\n}\n\nfn visit_statement(stmt: &Statement, prime_size: &BigInt, reports: &mut ReportCollection) {\n    use AssignOp::*;\n    use Expression::*;\n    use Statement::*;\n    use ValueReduction::*;\n    if let Substitution { meta: var_meta, op: AssignLocalOrComponent, rhe, .. } = stmt {\n        // If the variable `var` is declared as a local variable or signal, we exit early.\n        if var_meta.type_knowledge().is_local() || var_meta.type_knowledge().is_signal() {\n            return;\n        }\n        // If this is an update node, we extract the right-hand side.\n        let rhe = if let Update { rhe, .. } = rhe { rhe } else { rhe };\n\n        // A component initialization on the form `var = component_name(args, ...)`.\n        if let Call { meta: component_meta, name: component_name, args } = rhe {\n            // We assume this is the `Num2Bits` circuit from Circomlib.\n            if component_name == \"Num2Bits\" && args.len() == 1 {\n                let arg = &args[0];\n                // If the input size is known to be less than the prime size, this\n                // initialization is safe.\n                if let Some(FieldElement { value }) = arg.value() {\n                    if value < prime_size {\n                        return;\n                    }\n                }\n                reports.push(build_num2bits(component_meta));\n            }\n            // We assume this is the `Bits2Num` circuit from Circomlib.\n            if component_name == \"Bits2Num\" && args.len() == 1 {\n                let arg = &args[0];\n                // If the input size is known to be less than the prime size, this\n                // initialization is safe.\n                if let Some(FieldElement { value }) = arg.value() {\n                    if value < prime_size {\n                        return;\n                    }\n                }\n                reports.push(build_bits2num(component_meta));\n            }\n        }\n    }\n}\n\nfn build_num2bits(meta: &Meta) -> Report {\n    NonStrictBinaryConversionWarning::Num2Bits {\n        file_id: meta.file_id(),\n        location: meta.file_location(),\n    }\n    .into_report()\n}\n\nfn build_bits2num(meta: &Meta) -> Report {\n    NonStrictBinaryConversionWarning::Bits2Num {\n        file_id: meta.file_id(),\n        location: meta.file_location(),\n    }\n    .into_report()\n}\n\n#[cfg(test)]\nmod tests {\n    use parser::parse_definition;\n    use program_structure::{cfg::IntoCfg, constants::Curve};\n\n    use super::*;\n\n    #[test]\n    fn test_nonstrict_num2bits() {\n        let src = r#\"\n            template F(n) {\n                signal input in;\n                signal output out[n];\n\n                component n2b = Num2Bits(n);\n                n2b.in === in;\n                for (var i = 0; i < n; i++) {\n                    out[i] <== n2b.out[i];\n                }\n            }\n        \"#;\n        validate_reports(src, 1);\n\n        let src = r#\"\n            template F(n) {\n                signal input in;\n                signal output out[n];\n\n                var bits = 254;\n                component n2b = Num2Bits(bits - 1);\n                n2b.in === in;\n                for (var i = 0; i < n; i++) {\n                    out[i] <== n2b.out[i];\n                }\n            }\n        \"#;\n        validate_reports(src, 0);\n    }\n\n    fn validate_reports(src: &str, expected_len: usize) {\n        // Build CFG.\n        let mut reports = ReportCollection::new();\n        let cfg = parse_definition(src)\n            .unwrap()\n            .into_cfg(&Curve::Bn254, &mut reports)\n            .unwrap()\n            .into_ssa()\n            .unwrap();\n        assert!(reports.is_empty());\n\n        // Generate report collection.\n        let reports = find_nonstrict_binary_conversion(&cfg);\n\n        assert_eq!(reports.len(), expected_len);\n    }\n}\n"
  },
  {
    "path": "program_analysis/src/side_effect_analysis.rs",
    "content": "use log::debug;\nuse std::fmt::Write;\nuse std::collections::{HashMap, HashSet};\n\nuse program_structure::cfg::{Cfg, DefinitionType};\nuse program_structure::report_code::ReportCode;\nuse program_structure::report::{Report, ReportCollection};\nuse program_structure::file_definition::{FileID, FileLocation};\nuse program_structure::ir::declarations::Declaration;\nuse program_structure::ir::variable_meta::{VariableMeta, VariableUse};\nuse program_structure::ir::{Expression, SignalType, Statement, VariableType};\n\nuse crate::constraint_analysis::run_constraint_analysis;\nuse crate::taint_analysis::run_taint_analysis;\n\npub struct UnusedVariableWarning {\n    var: VariableUse,\n}\n\nimpl UnusedVariableWarning {\n    pub fn into_report(self) -> Report {\n        let mut report = Report::warning(\n            format!(\n                \"The variable `{}` is assigned a value, but this value is never read.\",\n                self.var\n            ),\n            ReportCode::UnusedVariableValue,\n        );\n        if let Some(file_id) = self.var.meta().file_id() {\n            report.add_primary(\n                self.var.meta().file_location(),\n                file_id,\n                format!(\"The value assigned to `{}` here is never read.\", self.var),\n            );\n        }\n        report\n    }\n}\npub struct UnconstrainedSignalWarning {\n    signal_name: String,\n    dimensions: Vec<Expression>,\n    file_id: Option<FileID>,\n    file_location: FileLocation,\n}\n\nimpl UnconstrainedSignalWarning {\n    pub fn into_report(self) -> Report {\n        if self.dimensions.is_empty() {\n            let mut report = Report::warning(\n                format!(\"The signal `{}` is not constrained by the template.\", self.signal_name),\n                ReportCode::UnconstrainedSignal,\n            );\n            if let Some(file_id) = self.file_id {\n                report.add_primary(\n                    self.file_location,\n                    file_id,\n                    \"This signal does not occur in a constraint.\".to_string(),\n                );\n            }\n            report\n        } else {\n            let mut report = Report::warning(\n                format!(\n                    \"The signals `{}{}` are not constrained by the template.\",\n                    self.signal_name,\n                    dimensions_to_string(&self.dimensions)\n                ),\n                ReportCode::UnconstrainedSignal,\n            );\n            if let Some(file_id) = self.file_id {\n                report.add_primary(\n                    self.file_location,\n                    file_id,\n                    \"These signals do not occur in a constraint.\".to_string(),\n                );\n            }\n            report\n        }\n    }\n}\n\npub struct UnusedSignalWarning {\n    signal_name: String,\n    dimensions: Vec<Expression>,\n    file_id: Option<FileID>,\n    file_location: FileLocation,\n}\n\nimpl UnusedSignalWarning {\n    pub fn into_report(self) -> Report {\n        if self.dimensions.is_empty() {\n            let mut report = Report::warning(\n                format!(\"The signal `{}` is not used by the template.\", self.signal_name),\n                ReportCode::UnusedVariableValue,\n            );\n            if let Some(file_id) = self.file_id {\n                report.add_primary(\n                    self.file_location,\n                    file_id,\n                    \"This signal is unused and could be removed.\".to_string(),\n                );\n            }\n            report\n        } else {\n            let mut report = Report::warning(\n                format!(\n                    \"The signals `{}{}` are not used by the template.\",\n                    self.signal_name,\n                    dimensions_to_string(&self.dimensions)\n                ),\n                ReportCode::UnusedVariableValue,\n            );\n            if let Some(file_id) = self.file_id {\n                report.add_primary(\n                    self.file_location,\n                    file_id,\n                    \"These signals are unused and could be removed.\".to_string(),\n                );\n            }\n            report\n        }\n    }\n}\n\npub struct UnusedParameterWarning {\n    param: VariableUse,\n    cfg_name: String,\n}\n\nimpl UnusedParameterWarning {\n    pub fn into_report(self) -> Report {\n        let mut report = Report::warning(\n            format!(\"The parameter `{}` is never read.\", self.param.name()),\n            ReportCode::UnusedParameterValue,\n        );\n        if let Some(file_id) = self.param.meta().file_id() {\n            report.add_primary(\n                self.param.meta().file_location(),\n                file_id,\n                format!(\n                    \"The parameter `{}` is never used in `{}`.\",\n                    self.param.name(),\n                    self.cfg_name\n                ),\n            );\n        }\n        report\n    }\n}\n\npub struct VariableWithoutSideEffectsWarning {\n    var: VariableUse,\n    cfg_type: DefinitionType,\n}\n\nimpl VariableWithoutSideEffectsWarning {\n    pub fn into_report(self) -> Report {\n        let (message, primary) = if matches!(self.cfg_type, DefinitionType::Function) {\n            let message = format!(\n                \"The value assigned to `{}` is not used to compute the return value.\",\n                self.var\n            );\n            let primary = format!(\n                \"The value assigned to `{}` here does not influence the return value.\",\n                self.var\n            );\n            (message, primary)\n        } else {\n            let message = format!(\n                \"The value assigned to `{}` is not used in witness or constraint generation.\",\n                self.var\n            );\n            let primary = format!(\"The value assigned to `{}` here does not influence witness or constraint generation.\", self.var);\n            (message, primary)\n        };\n        let mut report = Report::warning(message, ReportCode::VariableWithoutSideEffect);\n        if let Some(file_id) = self.var.meta().file_id() {\n            report.add_primary(self.var.meta().file_location(), file_id, primary);\n        }\n        report\n    }\n}\n\npub struct ParamWithoutSideEffectsWarning {\n    param: VariableUse,\n    cfg_type: DefinitionType,\n}\n\nimpl ParamWithoutSideEffectsWarning {\n    pub fn into_report(self) -> Report {\n        let (message, primary) = if matches!(self.cfg_type, DefinitionType::Function) {\n            let message = format!(\n                \"The parameter `{}` is not used to compute the return value of the function.\",\n                self.param\n            );\n            let primary = format!(\n                \"The parameter `{}` does not influence the return value and could be removed.\",\n                self.param\n            );\n            (message, primary)\n        } else {\n            let message = format!(\n                \"The parameter `{}` is not used in witness or constraint generation.\",\n                self.param\n            );\n            let primary = format!(\n                \"The parameter `{}` does not influence witness or constraint generation.\",\n                self.param\n            );\n            (message, primary)\n        };\n        let mut report = Report::warning(message, ReportCode::VariableWithoutSideEffect);\n        if let Some(file_id) = self.param.meta().file_id() {\n            report.add_primary(self.param.meta().file_location(), file_id, primary);\n        }\n        report\n    }\n}\n\n/// Local variables and intermediate signals that do not flow into either\n///\n///   1. an input or output signal,\n///   3. a function return value, or\n///   2. a constraint restricting and input or output signal\n///\n/// are side-effect free and do not affect either witness or constraint\n/// generation.\npub fn run_side_effect_analysis(cfg: &Cfg) -> ReportCollection {\n    debug!(\"running side-effect analysis pass\");\n\n    // 1. Run taint and constraint analysis to be able to track data flow.\n    let taint_analysis = run_taint_analysis(cfg);\n    let constraint_analysis = run_constraint_analysis(cfg);\n\n    // 2. Compute the set of variables read.\n    let mut variables_read = HashSet::new();\n    for basic_block in cfg.iter() {\n        variables_read.extend(basic_block.variables_read().map(|var| var.name().clone()));\n    }\n\n    // 3. Compute the set of sinks as follows:\n    //\n    //   1. Generate the set of input and output signals `A`.\n    //   2. Compute the set `B` of variables tainted by `A`.\n    //   3. Compute the set `C` of variables occurring in a\n    //      constraint together with an element from `B`.\n    //   4. Generate the set `D` of variables occurring in\n    //      a dimension expression in a declaration, in a\n    //      return value, or in an asserted value.\n    //\n    // The set of sinks is the union of A, C and D.\n\n    // Compute the set of input and output signals.\n    let signal_decls = cfg\n        .declarations()\n        .iter()\n        .filter_map(|(name, declaration)| {\n            if matches!(declaration.variable_type(), VariableType::Signal(_, _)) {\n                Some((name, declaration))\n            } else {\n                None\n            }\n        })\n        .collect::<HashMap<_, _>>();\n    let exported_signals = signal_decls\n        .iter()\n        .filter_map(|(name, declaration)| {\n            if matches!(\n                declaration.variable_type(),\n                VariableType::Signal(SignalType::Input | SignalType::Output, _)\n            ) {\n                Some(*name)\n            } else {\n                None\n            }\n        })\n        .cloned()\n        .collect::<HashSet<_>>();\n    // println!(\"exported signals: {exported_signals:?}\");\n\n    // Compute the set of variables tainted by input and output signals.\n    let exported_sinks = exported_signals\n        .iter()\n        .flat_map(|source| taint_analysis.multi_step_taint(source))\n        .collect::<HashSet<_>>();\n    // println!(\"exported sinks: {exported_sinks:?}\");\n\n    // Collect variables constraining input and output sinks.\n    let mut sinks = exported_sinks\n        .iter()\n        .flat_map(|source| {\n            let mut result = constraint_analysis.multi_step_constraint(source);\n            // If the source is part of a constraint we include it in the result.\n            if !result.is_empty() {\n                result.insert(source.clone());\n            }\n            result\n        })\n        .collect::<HashSet<_>>();\n\n    // Add input and output signals to this set.\n    sinks.extend(exported_signals);\n    // println!(\"constraint sinks: {sinks:?}\");\n\n    // Add variables occurring in declarations, return values, asserts, and\n    // control-flow conditions.\n    use Statement::*;\n    for basic_block in cfg.iter() {\n        for stmt in basic_block.iter() {\n            match stmt {\n                Declaration { .. } | Return { .. } | Assert { .. } | IfThenElse { .. } => {\n                    // If a variable used in a dimension expression is side-effect free,\n                    // the declared variable must also be side-effect free.\n                    sinks.extend(stmt.variables_read().map(|var| var.name().clone()));\n                }\n                _ => {}\n            }\n        }\n    }\n    // println!(\"all sinks: {sinks:?}\");\n    // println!(\"variables read: {variables_read:?}\");\n\n    let mut reports = ReportCollection::new();\n    let mut reported_vars = HashSet::new();\n\n    // Generate a report for any variable that does not taint a sink.\n    //\n    // TODO: The call to TaintAnalysis::taints_any chokes on CFGs containing\n    // large (65536 element) arrays.\n    for source in taint_analysis.definitions() {\n        // Circom 2.1.2 introduces `_` for ignored variables in tuple\n        // assignments. We respect this convention here as well.\n        if source.to_string() == \"_\" {\n            continue;\n        }\n        if !variables_read.contains(source.name()) {\n            // If the variable is unread, the corresponding value is unused.\n            if cfg.parameters().contains(source.name()) {\n                reports.push(build_unused_param(source, cfg.name()))\n            } else {\n                reports.push(build_unused_variable(source));\n            }\n            reported_vars.insert(source.name().to_string());\n        } else if !taint_analysis.taints_any(source.name(), &sinks) {\n            // If the variable does not flow into any of the sinks, it is side-effect free.\n            if cfg.parameters().contains(source.name()) {\n                reports.push(build_param_without_side_effect(source, cfg.definition_type()));\n            } else {\n                reports.push(build_variable_without_side_effect(source, cfg.definition_type()));\n            }\n            reported_vars.insert(source.name().to_string());\n        }\n    }\n    // Generate reports for unused or unconstrained signals.\n    //\n    // TODO: The call to TaintAnalysis::taints_any chokes on CFGs containing\n    // large (65536 element) arrays.\n    for (source, declaration) in signal_decls {\n        // Circom 2.1.2 introduces `_` for ignored variables in tuple\n        // assignments. We respect this convention here as well.\n        if source.to_string() == \"_\" {\n            continue;\n        }\n        // Don't generate multiple reports for the same variable.\n        if reported_vars.contains(&source.to_string()) {\n            continue;\n        }\n        if !variables_read.contains(source) {\n            // If the variable is unread, it must be unconstrained.\n            reports.push(build_unused_signal(declaration));\n        } else if matches!(cfg.definition_type(), DefinitionType::Template)\n            && !taint_analysis.taints_any(source, &constraint_analysis.constrained_variables())\n        {\n            // If the signal does not flow to a constraint, it is unconstrained.\n            // (Note that we exclude functions and custom templates here since\n            // they are not allowed to contain constraints.)\n            reports.push(build_unconstrained_signal(declaration));\n        }\n    }\n    reports\n}\n\nfn build_unused_variable(definition: &VariableUse) -> Report {\n    UnusedVariableWarning { var: definition.clone() }.into_report()\n}\n\nfn build_unused_param(definition: &VariableUse, cfg_name: &str) -> Report {\n    UnusedParameterWarning { param: definition.clone(), cfg_name: cfg_name.to_string() }\n        .into_report()\n}\n\nfn build_unused_signal(declaration: &Declaration) -> Report {\n    UnusedSignalWarning {\n        signal_name: declaration.variable_name().to_string(),\n        dimensions: declaration.dimensions().clone(),\n        file_id: declaration.file_id(),\n        file_location: declaration.file_location(),\n    }\n    .into_report()\n}\n\nfn build_unconstrained_signal(declaration: &Declaration) -> Report {\n    UnconstrainedSignalWarning {\n        signal_name: declaration.variable_name().to_string(),\n        dimensions: declaration.dimensions().clone(),\n        file_id: declaration.file_id(),\n        file_location: declaration.file_location(),\n    }\n    .into_report()\n}\n\nfn build_variable_without_side_effect(\n    definition: &VariableUse,\n    cfg_type: &DefinitionType,\n) -> Report {\n    VariableWithoutSideEffectsWarning { var: definition.clone(), cfg_type: cfg_type.clone() }\n        .into_report()\n}\n\nfn build_param_without_side_effect(definition: &VariableUse, cfg_type: &DefinitionType) -> Report {\n    ParamWithoutSideEffectsWarning { param: definition.clone(), cfg_type: cfg_type.clone() }\n        .into_report()\n}\n\nfn dimensions_to_string(dimensions: &[Expression]) -> String {\n    let mut result = String::new();\n    for size in dimensions {\n        // We ignore errors here.\n        let _ = write!(result, \"[{}]\", size);\n    }\n    result\n}\n\n#[cfg(test)]\nmod tests {\n    use parser::parse_definition;\n    use program_structure::{cfg::IntoCfg, constants::Curve};\n\n    use super::*;\n\n    #[test]\n    fn test_side_effect_analysis() {\n        let src = r#\"\n            template T(n) {\n              signal input in;\n              signal output out[n];\n\n              var lin = in * in;\n              var lout = 0;  // The value assigned here is side-effect free.\n              var nout = 0;\n\n              var e = 1;  // The value assigned here is side-effect free.\n              for (var k = 0; k < n; k++) {\n                out[k] <-- (in >> k) & 1;\n                out[k] * (out[k] - 1) === 0;\n\n                lout += out[k] * e;  // The value assigned here is side-effect free.\n                e = e + e;  // The value assigned here is side-effect free.\n              }\n\n              lin === nout;  // Should use `lout`, but uses `nout` by mistake.\n            }\n        \"#;\n        validate_reports(src, 4);\n\n        let src = r#\"\n            template PointOnLine(k, m, n) {\n                signal input in[2];\n\n                var LOGK = log2(k);\n                var LOGK2 = log2(k * k);\n                assert(3 * n + LOGK2 < 251);\n\n                component left = BigTemplate(n, k, 2 * n + LOGK + 1);\n                component right[m];\n                for (var i = 0; i < n; i++) {\n                    right[i] = SmallTemplate(k);\n                }\n                left.a <== right[0].a;\n                left.b <== right[0].b;\n            }\n        \"#;\n        validate_reports(src, 4);\n\n        let src = r#\"\n            template Sum(n) {\n                signal input in[n];\n                signal output out[n];\n\n                var e = 1;\n                var lin = 0;\n                for (var i = 0; i < n; i++) {\n                    lin += in[i] * e;\n                    e += e;\n                }\n\n                var lout = 0;\n                for (var i = 0; i < n; i++) {\n                    lout += out[i];\n                }\n\n                lin === lout;\n            }\n        \"#;\n        validate_reports(src, 0);\n\n        let src = r#\"\n            template T(n) {\n                signal tmp[n];\n\n                tmp[0] <-- 0;\n            }\n        \"#;\n        validate_reports(src, 1);\n\n        let src = r#\"\n            template T() {\n                signal input in[2];\n                signal output out;\n                component c = C();\n                signal s;\n\n                c.in <== 2 * s;\n                out <== in[0] * in[1];\n\n            }\n        \"#;\n        validate_reports(src, 1);\n\n        let src = r#\"\n            template T() {\n                signal input in[2];\n                signal output out;\n                component n2b = Num2Bits();\n\n                n2b.in <== in[0] * in[1] + 1;\n                out <== in[0] * in[1];\n\n            }\n        \"#;\n        validate_reports(src, 0);\n\n        // TODO: The assignment to `tmp` should be detected.\n        let src = r#\"\n            template T() {\n                signal input in[2];\n                signal output out;\n                signal tmp;\n\n                tmp <== 2 * in[0];\n                out <== in[0] * in[1];\n\n            }\n        \"#;\n        validate_reports(src, 0);\n    }\n\n    fn validate_reports(src: &str, expected_len: usize) {\n        // Build CFG.\n        let mut reports = ReportCollection::new();\n        let cfg = parse_definition(src)\n            .unwrap()\n            .into_cfg(&Curve::default(), &mut reports)\n            .unwrap()\n            .into_ssa()\n            .unwrap();\n        assert!(reports.is_empty());\n\n        // Generate report collection.\n        let reports = run_side_effect_analysis(&cfg);\n        assert_eq!(reports.len(), expected_len);\n    }\n}\n"
  },
  {
    "path": "program_analysis/src/signal_assignments.rs",
    "content": "use log::{debug, trace};\nuse program_structure::intermediate_representation::degree_meta::{DegreeRange, DegreeMeta};\nuse std::collections::HashSet;\n\nuse program_structure::cfg::{Cfg, DefinitionType};\nuse program_structure::report_code::ReportCode;\nuse program_structure::report::{Report, ReportCollection};\nuse program_structure::ir::*;\nuse program_structure::ir::AccessType;\nuse program_structure::ir::variable_meta::VariableMeta;\n\npub struct SignalAssignmentWarning {\n    signal: VariableName,\n    access: Vec<AccessType>,\n    assignment_meta: Meta,\n    constraint_metas: Vec<Meta>,\n}\n\nimpl SignalAssignmentWarning {\n    pub fn into_report(self) -> Report {\n        let mut report = Report::warning(\n            \"Using the signal assignment operator `<--` does not constrain the assigned signal.\"\n                .to_string(),\n            ReportCode::SignalAssignmentStatement,\n        );\n        // Add signal assignment warning.\n        if let Some(file_id) = self.assignment_meta.file_id {\n            report.add_primary(\n                self.assignment_meta.location,\n                file_id,\n                format!(\n                    \"The assigned signal `{}{}` is not constrained here.\",\n                    self.signal,\n                    access_to_string(&self.access)\n                ),\n            );\n        }\n        // Add any constraints as secondary labels.\n        for meta in self.constraint_metas {\n            if let Some(file_id) = meta.file_id {\n                report.add_secondary(\n                    meta.location,\n                    file_id,\n                    Some(format!(\n                        \"The signal `{}{}` is constrained here.\",\n                        self.signal,\n                        access_to_string(&self.access),\n                    )),\n                );\n            }\n        }\n        // If no constraints are identified, suggest using `<==` instead.\n        if report.secondary().is_empty() {\n            report.add_note(\n                \"Consider if it is possible to rewrite the statement using `<==` instead.\"\n                    .to_string(),\n            );\n        }\n        report\n    }\n}\n\npub struct UnecessarySignalAssignmentWarning {\n    signal: VariableName,\n    access: Vec<AccessType>,\n    assignment_meta: Meta,\n}\n\nimpl UnecessarySignalAssignmentWarning {\n    pub fn into_report(self) -> Report {\n        let mut report = Report::warning(\n            \"Using the signal assignment operator `<--` is not necessary here.\".to_string(),\n            ReportCode::UnnecessarySignalAssignment,\n        );\n        // Add signal assignment warning.\n        if let Some(file_id) = self.assignment_meta.file_id {\n            report.add_primary(\n                self.assignment_meta.location,\n                file_id,\n                format!(\n                    \"The expression assigned to `{}{}` is quadratic.\",\n                    self.signal,\n                    access_to_string(&self.access)\n                ),\n            );\n        }\n        // We always suggest using `<==` instead.\n        report.add_note(\n            \"Consider rewriting the statement using the constraint assignment operator `<==`.\"\n                .to_string(),\n        );\n        report\n    }\n}\n\ntype AssignmentSet = HashSet<Assignment>;\n/// A signal assignment (implemented using either `<--` or `<==`).\n#[derive(Clone, Hash, PartialEq, Eq)]\nstruct Assignment {\n    pub meta: Meta,\n    pub signal: VariableName,\n    pub access: Vec<AccessType>,\n    pub degree: Option<DegreeRange>,\n}\n\nimpl Assignment {\n    fn new(\n        meta: &Meta,\n        signal: &VariableName,\n        access: &[AccessType],\n        degree: Option<&DegreeRange>,\n    ) -> Assignment {\n        Assignment {\n            meta: meta.clone(),\n            signal: signal.clone(),\n            access: access.to_owned(),\n            degree: degree.cloned(),\n        }\n    }\n\n    fn is_quadratic(&self) -> bool {\n        if let Some(range) = &self.degree {\n            range.is_quadratic()\n        } else {\n            false\n        }\n    }\n}\n\ntype ConstraintSet = HashSet<Constraint>;\n\n#[derive(Clone, Hash, PartialEq, Eq)]\nstruct Constraint {\n    pub meta: Meta,\n    pub lhe: Expression,\n    pub rhe: Expression,\n}\n\nimpl Constraint {\n    fn new(meta: &Meta, lhe: &Expression, rhe: &Expression) -> Constraint {\n        Constraint { meta: meta.clone(), lhe: lhe.clone(), rhe: rhe.clone() }\n    }\n}\n\n/// This structure tracks signal assignments and constraints in a single\n/// template.\n#[derive(Clone, Default)]\nstruct SignalUse {\n    assignments: AssignmentSet,\n    constraints: ConstraintSet,\n}\n\nimpl SignalUse {\n    /// Create a new `ConstraintInfo` instance.\n    fn new() -> SignalUse {\n        SignalUse::default()\n    }\n\n    /// Add a signal assignment `var[access] <-- expr`.\n    fn add_assignment(\n        &mut self,\n        var: &VariableName,\n        access: &[AccessType],\n        meta: &Meta,\n        degree: Option<&DegreeRange>,\n    ) {\n        trace!(\"adding signal assignment for `{var:?}` access\");\n        self.assignments.insert(Assignment::new(meta, var, access, degree));\n    }\n\n    /// Add a constraint `lhe === rhe`.\n    fn add_constraint(&mut self, lhe: &Expression, rhe: &Expression, meta: &Meta) {\n        trace!(\"adding constraint `{lhe:?} === {rhe:?}`\");\n        self.constraints.insert(Constraint::new(meta, lhe, rhe));\n    }\n\n    /// Get all assignments.\n    fn get_assignments(&self) -> &AssignmentSet {\n        &self.assignments\n    }\n\n    /// Get the set of constraints that contain the given variable.\n    fn get_constraints(&self, signal: &VariableName, access: &Vec<AccessType>) -> Vec<&Constraint> {\n        self.constraints\n            .iter()\n            .filter(|constraint| {\n                let lhe = constraint.lhe.signals_read().iter();\n                let rhe = constraint.rhe.signals_read().iter();\n                lhe.chain(rhe)\n                    .any(|signal_use| signal_use.name() == signal && signal_use.access() == access)\n            })\n            .collect()\n    }\n\n    /// Returns the corresponding `Meta` of a constraint containing the given\n    /// signal, or `None` if no such constraint exists.\n    fn get_constraint_metas(&self, signal: &VariableName, access: &Vec<AccessType>) -> Vec<Meta> {\n        self.get_constraints(signal, access)\n            .iter()\n            .map(|constraint| constraint.meta.clone())\n            .collect()\n    }\n}\n\n/// The signal assignment operator `y <-- x` does not constrain the signal `y`.\n/// If the developer meant to use the constraint assignment operator `<==` this\n/// could lead to unexpected results.\npub fn find_signal_assignments(cfg: &Cfg) -> ReportCollection {\n    use DefinitionType::*;\n    if matches!(cfg.definition_type(), Function | CustomTemplate) {\n        // Exit early if this is a function or custom template.\n        return ReportCollection::new();\n    }\n    debug!(\"running signal assignment analysis pass\");\n    let mut signal_use = SignalUse::new();\n    for basic_block in cfg.iter() {\n        for stmt in basic_block.iter() {\n            visit_statement(stmt, &mut signal_use);\n        }\n    }\n    let mut reports = ReportCollection::new();\n    for assignment in signal_use.get_assignments() {\n        if assignment.is_quadratic() {\n            reports.push(build_unecessary_assignment_report(\n                &assignment.signal,\n                &assignment.access,\n                &assignment.meta,\n            ))\n        } else {\n            let constraint_metas =\n                signal_use.get_constraint_metas(&assignment.signal, &assignment.access);\n            reports.push(build_assignment_report(\n                &assignment.signal,\n                &assignment.access,\n                &assignment.meta,\n                &constraint_metas,\n            ));\n        }\n    }\n\n    debug!(\"{} new reports generated\", reports.len());\n    reports\n}\n\nfn visit_statement(stmt: &Statement, signal_use: &mut SignalUse) {\n    use Expression::*;\n    use Statement::*;\n    match stmt {\n        Substitution { meta, var, op, rhe } => {\n            let access = if let Update { access, .. } = rhe { access.clone() } else { Vec::new() };\n            match op {\n                AssignOp::AssignSignal => {\n                    signal_use.add_assignment(var, &access, meta, rhe.degree());\n                }\n                // A signal cannot occur as the LHS of both a signal assignment\n                // and a signal constraint assignment. However, we still need to\n                // record the constraint added for each constraint assignment\n                // found.\n                AssignOp::AssignConstraintSignal => {\n                    let lhe = Expression::Variable { meta: meta.clone(), name: var.clone() };\n                    signal_use.add_constraint(&lhe, rhe, meta)\n                }\n                AssignOp::AssignLocalOrComponent => {}\n            }\n        }\n        ConstraintEquality { meta, lhe, rhe } => {\n            signal_use.add_constraint(lhe, rhe, meta);\n        }\n        _ => {}\n    }\n}\n\nfn build_unecessary_assignment_report(\n    signal: &VariableName,\n    access: &[AccessType],\n    assignment_meta: &Meta,\n) -> Report {\n    UnecessarySignalAssignmentWarning {\n        signal: signal.clone(),\n        access: access.to_owned(),\n        assignment_meta: assignment_meta.clone(),\n    }\n    .into_report()\n}\n\nfn build_assignment_report(\n    signal: &VariableName,\n    access: &[AccessType],\n    assignment_meta: &Meta,\n    constraint_metas: &[Meta],\n) -> Report {\n    SignalAssignmentWarning {\n        signal: signal.clone(),\n        access: access.to_owned(),\n        assignment_meta: assignment_meta.clone(),\n        constraint_metas: constraint_metas.to_owned(),\n    }\n    .into_report()\n}\n\n#[must_use]\nfn access_to_string(access: &[AccessType]) -> String {\n    access.iter().map(|access| access.to_string()).collect::<Vec<String>>().join(\"\")\n}\n\n#[cfg(test)]\nmod tests {\n    use parser::parse_definition;\n    use program_structure::{cfg::IntoCfg, constants::Curve};\n\n    use super::*;\n\n    #[test]\n    fn test_signal_assignments() {\n        let src = r#\"\n            template T(a) {\n                signal input in;\n                signal output out;\n\n                out <-- in + a;\n            }\n        \"#;\n        validate_reports(src, 1);\n\n        let src = r#\"\n            template T(a) {\n                signal input in;\n                signal output out;\n\n                in + a === out;\n                out <-- in + a;\n            }\n        \"#;\n        validate_reports(src, 1);\n\n        let src = r#\"\n            template T(n) {\n                signal input in;\n                signal output out[n];\n\n                in + 1 === out[0];\n                out[0] <-- in + 1;\n            }\n        \"#;\n        validate_reports(src, 1);\n\n        let src = r#\"\n            template T(n) {\n                signal output out[n];\n\n                in + 1 === out[0];\n                out[0] <-- in * in;\n            }\n        \"#;\n        validate_reports(src, 1);\n    }\n\n    fn validate_reports(src: &str, expected_len: usize) {\n        // Build CFG.\n        println!(\"{}\", src);\n        let mut reports = ReportCollection::new();\n        let cfg = parse_definition(src)\n            .unwrap()\n            .into_cfg(&Curve::default(), &mut reports)\n            .unwrap()\n            .into_ssa()\n            .unwrap();\n        assert!(reports.is_empty());\n\n        // Generate report collection.\n        let reports = find_signal_assignments(&cfg);\n        for report in &reports {\n            println!(\"{}\", report.message())\n        }\n\n        assert_eq!(reports.len(), expected_len);\n    }\n}\n"
  },
  {
    "path": "program_analysis/src/taint_analysis.rs",
    "content": "use log::{debug, trace};\nuse program_structure::cfg::parameters::Parameters;\nuse program_structure::intermediate_representation::value_meta::ValueMeta;\nuse program_structure::intermediate_representation::Meta;\nuse std::collections::{HashMap, HashSet};\n\nuse program_structure::cfg::Cfg;\nuse program_structure::ir::variable_meta::{VariableMeta, VariableUse};\nuse program_structure::ir::{Expression, Statement, VariableName};\n\n#[derive(Clone, Default)]\npub struct TaintAnalysis {\n    taint_map: HashMap<VariableName, HashSet<VariableName>>,\n    declarations: HashMap<VariableName, VariableUse>,\n    definitions: HashMap<VariableName, VariableUse>,\n}\n\nimpl TaintAnalysis {\n    fn new(parameters: &Parameters) -> TaintAnalysis {\n        // Add parameter definitions to taint analysis.\n        let mut result = TaintAnalysis::default();\n        let meta = Meta::new(parameters.file_location(), parameters.file_id());\n        for name in parameters.iter() {\n            trace!(\"adding parameter declaration for `{name:?}`\");\n            let definition = VariableUse::new(&meta, name, &Vec::new());\n            result.add_definition(&definition);\n        }\n        result\n    }\n\n    /// Add the variable use corresponding to the definition of the variable.\n    fn add_definition(&mut self, var: &VariableUse) {\n        // TODO: Since we don't version components and signals, we may end up\n        // overwriting component initializations here. For example, in the\n        // following case the component initialization will be clobbered.\n        //\n        //   component c[2];\n        //   ...\n        //   c[0].in[0] <== 0;\n        //   c[1].in[1] <== 1;\n        //\n        // As long as the initialized component flows to a constraint it will\n        // not be flagged during side-effect analysis.\n        self.definitions.insert(var.name().clone(), var.clone());\n    }\n\n    /// Get the variable use corresponding to the definition of the variable.\n    pub fn get_definition(&self, var: &VariableName) -> Option<VariableUse> {\n        self.definitions.get(var).cloned()\n    }\n\n    pub fn definitions(&self) -> impl Iterator<Item = &VariableUse> {\n        self.definitions.values()\n    }\n\n    /// Add the variable use corresponding to the declaration of the variable.\n    fn add_declaration(&mut self, var: &VariableUse) {\n        self.declarations.insert(var.name().clone(), var.clone());\n    }\n\n    /// Get the variable use corresponding to the declaration of the variable.\n    pub fn get_declaration(&self, var: &VariableName) -> Option<VariableUse> {\n        self.declarations.get(var).cloned()\n    }\n\n    pub fn declarations(&self) -> impl Iterator<Item = &VariableUse> {\n        self.declarations.values()\n    }\n\n    /// Add a single step taint from source to sink.\n    fn add_taint_step(&mut self, source: &VariableName, sink: &VariableName) {\n        let sinks = self.taint_map.entry(source.clone()).or_default();\n        sinks.insert(sink.clone());\n    }\n\n    /// Returns variables tainted in a single step by `source`.\n    pub fn single_step_taint(&self, source: &VariableName) -> HashSet<VariableName> {\n        self.taint_map.get(source).cloned().unwrap_or_default()\n    }\n\n    /// Returns variables tainted in zero or more steps by `source`.\n    pub fn multi_step_taint(&self, source: &VariableName) -> HashSet<VariableName> {\n        let mut result = HashSet::new();\n        let mut update = HashSet::from([source.clone()]);\n        while !update.is_subset(&result) {\n            result.extend(update.iter().cloned());\n            update = update.iter().flat_map(|source| self.single_step_taint(source)).collect();\n        }\n        result\n    }\n\n    /// Returns true if the source taints any of the sinks.\n    pub fn taints_any(&self, source: &VariableName, sinks: &HashSet<VariableName>) -> bool {\n        self.multi_step_taint(source).iter().any(|sink| sinks.contains(sink))\n    }\n}\n\npub fn run_taint_analysis(cfg: &Cfg) -> TaintAnalysis {\n    debug!(\"running taint analysis pass\");\n    let mut result = TaintAnalysis::new(cfg.parameters());\n\n    use Expression::*;\n    use Statement::*;\n    for basic_block in cfg.iter() {\n        for stmt in basic_block.iter() {\n            trace!(\"visiting statement `{stmt:?}`\");\n            match stmt {\n                Substitution { .. } => {\n                    // Variables read taint variables written by the statement.\n                    for sink in stmt.variables_written() {\n                        if !matches!(stmt, Substitution { rhe: Phi { .. }, .. }) {\n                            // Add the definition to the result.\n                            trace!(\"adding variable assignment for `{:?}`\", sink.name());\n                            result.add_definition(sink);\n                        }\n                        for source in stmt.variables_read() {\n                            // Add each taint step to the result.\n                            trace!(\n                                \"adding taint step with source `{:?}` and sink `{:?}`\",\n                                source.name(),\n                                sink.name()\n                            );\n                            result.add_taint_step(source.name(), sink.name());\n                        }\n                    }\n                }\n                Declaration { meta, names, dimensions, .. } => {\n                    // Variables occurring in declarations taint the declared variable.\n                    for sink in names {\n                        result.add_declaration(&VariableUse::new(meta, sink, &Vec::new()));\n                        for size in dimensions {\n                            for source in size.variables_read() {\n                                result.add_taint_step(source.name(), sink)\n                            }\n                        }\n                    }\n                }\n                IfThenElse { cond, .. } => {\n                    // A variable which occurs in a non-constant condition taints all\n                    // variables assigned in the if-statement body.\n                    if cond.value().is_some() {\n                        continue;\n                    }\n                    let true_branch = cfg.get_true_branch(basic_block);\n                    let false_branch = cfg.get_false_branch(basic_block);\n                    for body in true_branch.iter().chain(false_branch.iter()) {\n                        // Add taint for assigned variables.\n                        for sink in body.variables_written() {\n                            for source in cond.variables_read() {\n                                // Add each taint step to the result.\n                                trace!(\n                                    \"adding taint step with source `{:?}` and sink `{:?}`\",\n                                    source.name(),\n                                    sink.name()\n                                );\n                                result.add_taint_step(source.name(), sink.name());\n                            }\n                        }\n                    }\n                }\n                // The following statement types do not propagate taint.\n                Assert { .. } | LogCall { .. } | Return { .. } | ConstraintEquality { .. } => {}\n            }\n        }\n    }\n    result\n}\n\n#[cfg(test)]\nmod tests {\n    use std::collections::HashMap;\n\n    use parser::parse_definition;\n    use program_structure::cfg::IntoCfg;\n    use program_structure::constants::Curve;\n    use program_structure::report::ReportCollection;\n\n    use super::*;\n\n    #[test]\n    fn test_taint_analysis() {\n        let src = r#\"\n            template PointOnLine(k, m, n) {\n                signal input in[2];\n\n                var LOGK = log2(k);\n                var LOGK2 = log2(3 * k * k);\n                assert(3 * n + LOGK2 < 251);\n\n                component left = BigTemplate(n, k, 2 * n + LOGK + 1);\n                left.a <== in[0];\n                left.b <== in[1];\n\n                component right[m];\n                for (var i = 0; i < n; i++) {\n                    right[0] = SmallTemplate(k);\n                }\n            }\n        \"#;\n\n        let mut taint_map = HashMap::new();\n        taint_map.insert(\n            \"k\",\n            HashSet::from([\n                \"k\".to_string(),\n                \"LOGK\".to_string(),\n                \"LOGK2\".to_string(),\n                \"left\".to_string(),\n                \"right\".to_string(),\n            ]),\n        );\n        taint_map.insert(\n            \"m\",\n            HashSet::from([\n                \"m\".to_string(),\n                \"right\".to_string(), // Since `right` is declared as an `m` dimensional array.\n            ]),\n        );\n        taint_map.insert(\n            \"n\",\n            HashSet::from([\n                \"n\".to_string(),\n                \"i\".to_string(), // Since the update `i++` depends on the condition `i < n`.\n                \"left\".to_string(),\n                \"right\".to_string(),\n            ]),\n        );\n        taint_map.insert(\"i\", HashSet::from([\"i\".to_string(), \"right\".to_string()]));\n\n        validate_taint(src, &taint_map);\n    }\n\n    fn validate_taint(src: &str, taint_map: &HashMap<&str, HashSet<String>>) {\n        // Build CFG.\n        let mut reports = ReportCollection::new();\n        let cfg = parse_definition(src)\n            .unwrap()\n            .into_cfg(&Curve::default(), &mut reports)\n            .unwrap()\n            .into_ssa()\n            .unwrap();\n        assert!(reports.is_empty());\n\n        let taint_analysis = run_taint_analysis(&cfg);\n        for (source, expected_sinks) in taint_map {\n            let source = VariableName::from_string(source).with_version(0);\n            let sinks = taint_analysis\n                .multi_step_taint(&source)\n                .iter()\n                .map(|var| var.name().to_string())\n                .collect::<HashSet<_>>();\n            assert_eq!(&sinks, expected_sinks);\n        }\n    }\n}\n"
  },
  {
    "path": "program_analysis/src/unconstrained_division.rs",
    "content": "use std::collections::HashMap;\nuse std::fmt;\n\nuse log::{debug, trace};\n\nuse num_traits::Zero;\nuse program_structure::cfg::Cfg;\nuse program_structure::intermediate_representation::value_meta::{ValueReduction, ValueMeta};\nuse program_structure::ir::degree_meta::DegreeMeta;\nuse program_structure::report_code::ReportCode;\nuse program_structure::report::{Report, ReportCollection};\nuse program_structure::file_definition::{FileID, FileLocation};\nuse program_structure::ir::*;\n\npub struct UnconstrainedDivisionWarning {\n    divisor: Expression,\n    file_id: Option<FileID>,\n    file_location: FileLocation,\n}\n\nimpl UnconstrainedDivisionWarning {\n    pub fn into_report(self) -> Report {\n        let mut report = Report::warning(\n            \"In signal assignments containing division, the divisor needs to be constrained to be non-zero\".to_string(),\n            ReportCode::UnconstrainedDivision,\n        );\n        if let Some(file_id) = self.file_id {\n            report.add_primary(\n                self.file_location,\n                file_id,\n                format!(\"The divisor `{}` must be constrained to be non-zero.\", self.divisor),\n            );\n        }\n        report\n    }\n}\n\n#[derive(Eq, PartialEq, Hash)]\nstruct VariableAccess {\n    pub var: VariableName,\n    pub access: Vec<AccessType>,\n}\n\nimpl VariableAccess {\n    fn new(var: &VariableName, access: &[AccessType]) -> Self {\n        // We disregard the version to make sure accesses are not order dependent.\n        VariableAccess { var: var.without_version(), access: access.to_vec() }\n    }\n}\n\n/// Tracks `IsZero` template instantiations and uses.\n#[derive(Default)]\nstruct Component {\n    pub input: Option<Expression>,\n    pub output: Option<Expression>,\n}\n\nimpl Component {\n    fn new() -> Self {\n        Component::default()\n    }\n\n    fn ensures_nonzero(&self, expr: &Expression) -> bool {\n        if let Some(input) = self.input.as_ref() {\n            // This component ensures that the given expression is non-zero if\n            //   1. The component input is the given expression\n            //   2. The component output evaluates to false\n            expr == input && matches!(self.output(), Some(false))\n        } else {\n            false\n        }\n    }\n\n    fn output(&self) -> Option<bool> {\n        use ValueReduction::*;\n        let value = self.output.as_ref().and_then(|output| output.value());\n        match value {\n            Some(FieldElement { value }) => Some(!value.is_zero()),\n            Some(Boolean { value }) => Some(*value),\n            None => None,\n        }\n    }\n}\n\n/// Since division is not expressible as a quadratic constraint, it is common to\n/// perform division using the following pattern.\n///\n///   `c <-- a / b`\n///   `b * c === a`\n///\n/// That is, we assign the result of `a / b` to `c` during the proof generation,\n/// and the constrain `a`, `b`, and `c` to ensure that `b * c = a` during proof\n/// verification. However, this implicitly assumes that `b` is non-zero when the\n/// proof is generated, which needs to be verified separately when the proof is\n/// verified.\n///\n/// This analysis pass looks for signal assignments on the form `c <-- a / b`\n/// where the signal `b` is not constrained to be non-zero using the `IsZero`\n/// circuit from Circomlib.\npub fn find_unconstrained_division(cfg: &Cfg) -> ReportCollection {\n    debug!(\"running unconstrained divisor analysis pass\");\n    let mut reports = ReportCollection::new();\n    let mut divisors = Vec::new();\n    let mut constraints = HashMap::new();\n    for basic_block in cfg.iter() {\n        for stmt in basic_block.iter() {\n            update_divisors(stmt, &mut divisors);\n        }\n    }\n    if divisors.is_empty() {\n        return reports;\n    }\n\n    for basic_block in cfg.iter() {\n        for stmt in basic_block.iter() {\n            update_constraints(stmt, &mut constraints);\n        }\n    }\n    for divisor in divisors {\n        let mut non_zero = false;\n        for constraint in constraints.values() {\n            if constraint.ensures_nonzero(&divisor) {\n                non_zero = true;\n                break;\n            }\n        }\n        if !non_zero {\n            reports.push(build_report(&divisor));\n        }\n    }\n    debug!(\"{} new reports generated\", reports.len());\n    reports\n}\n\nfn update_divisors(stmt: &Statement, divisors: &mut Vec<Expression>) {\n    use AssignOp::*;\n    use Statement::*;\n    use Expression::*;\n    use ExpressionInfixOpcode::*;\n    // Identify signal assignment on the form `c <-- a / b`.\n    if let Substitution { op: AssignSignal, rhe, .. } = stmt {\n        // If this is an update node, we extract the right-hand side.\n        let rhe = if let Update { rhe, .. } = rhe { rhe } else { rhe };\n\n        // If the assigned expression is on the form `a / b`, where `b` may be non-constant, we store the divisor `b`.\n        if let InfixOp { infix_op: Div, rhe, .. } = rhe {\n            match rhe.degree() {\n                Some(range) if !range.is_constant() => {\n                    divisors.push(*rhe.clone());\n                }\n                None => {\n                    divisors.push(*rhe.clone());\n                }\n                _ => {}\n            }\n        }\n    }\n}\n\nfn update_constraints(stmt: &Statement, constraints: &mut HashMap<VariableAccess, Component>) {\n    use AssignOp::*;\n    use Statement::*;\n    use Expression::*;\n    use AccessType::*;\n    match stmt {\n        // Identify `IsZero` template instantiations.\n        Substitution { meta, var, op: AssignLocalOrComponent, rhe } => {\n            // If the variable `var` is declared as a local variable or signal, we exit early.\n            if meta.type_knowledge().is_local() || meta.type_knowledge().is_signal() {\n                return;\n            }\n            // If this is an assignment on the form `var[i] = T(...)` we need to store the access and obtain the RHS.\n            let (rhe, access) = if let Update { access, rhe, .. } = rhe {\n                (rhe.as_ref(), access.clone())\n            } else {\n                (rhe, Vec::new())\n            };\n            if let Call { name: component_name, args, .. } = rhe {\n                if component_name == \"IsZero\" && args.is_empty() {\n                    // We assume this is the `IsZero` circuit from Circomlib.\n                    trace!(\n                        \"`IsZero` template instantiation `{var}{}` found\",\n                        vec_to_display(&access, \"\")\n                    );\n                    let component = VariableAccess::new(var, &access);\n                    constraints.insert(component, Component::new());\n                }\n            }\n        }\n        // Identify `IsZero` input signal assignments.\n        Substitution {\n            var, op: AssignConstraintSignal, rhe: Update { access, rhe, .. }, ..\n        } => {\n            // If this is a `Num2Bits` input signal assignment, the input signal\n            // access would be the last element of the `access` vector.\n            let mut component_access = access.clone();\n            let signal_access = component_access.pop();\n            let component = VariableAccess::new(var, &component_access);\n            if let Some(constraint) = constraints.get_mut(&component) {\n                // This is a signal assignment to either the input or output if `IsZero`.\n                if let Some(ComponentAccess(signal_name)) = signal_access {\n                    if signal_name == \"in\" {\n                        constraint.input = Some(*rhe.clone());\n                    }\n                }\n            }\n        }\n        // Identify `IsZero` output signal constraints on the form `var[i].out === expr`.\n        ConstraintEquality { lhe: Access { var, access, .. }, rhe, .. } => {\n            // Assume LHS is the `IsZero` output signal.\n            let mut component_access = access.clone();\n            let signal_access = component_access.pop();\n            let component = VariableAccess::new(var, &component_access);\n            if let Some(constraint) = constraints.get_mut(&component) {\n                if let Some(ComponentAccess(signal_name)) = signal_access {\n                    if signal_name == \"out\" {\n                        constraint.output = Some(rhe.clone());\n                    }\n                }\n            }\n        }\n        // Identify `IsZero` output signal constraints on the form `expr === var[i].out ===`.\n        ConstraintEquality { lhe, rhe: Access { var, access, .. }, .. } => {\n            // Assume RHS is the `IsZero` output signal.\n            let mut component_access = access.clone();\n            let signal_access = component_access.pop();\n            let component = VariableAccess::new(var, &component_access);\n            if let Some(constraint) = constraints.get_mut(&component) {\n                if let Some(ComponentAccess(signal_name)) = signal_access {\n                    if signal_name == \"out\" {\n                        constraint.output = Some(lhe.clone());\n                    }\n                }\n            }\n        }\n        // By default we do nothing.\n        _ => {}\n    }\n}\n\n#[must_use]\nfn build_report(divisor: &Expression) -> Report {\n    UnconstrainedDivisionWarning {\n        divisor: divisor.clone(),\n        file_id: divisor.meta().file_id,\n        file_location: divisor.meta().file_location(),\n    }\n    .into_report()\n}\n\n#[must_use]\nfn vec_to_display<T: fmt::Display>(elems: &[T], sep: &str) -> String {\n    elems.iter().map(|elem| format!(\"{elem}\")).collect::<Vec<String>>().join(sep)\n}\n\n#[cfg(test)]\nmod tests {\n    use parser::parse_definition;\n    use program_structure::{cfg::IntoCfg, constants::Curve};\n\n    use super::*;\n\n    #[test]\n    fn test_unconstrained_less_than() {\n        let src = r#\"\n            template Test(n) {\n              signal input a;\n              signal input b;\n              signal output c;\n\n              c <-- a / b;\n              c * b === a;\n            }\n        \"#;\n        validate_reports(src, 1);\n\n        let src = r#\"\n            template Test(n) {\n              signal input a[n];\n              signal input b[n];\n              signal output c[n];\n\n              for (var i = 0; i < n; i++) {\n                c[i] <-- a[i] / b[i];\n                c[i] * b[i] === a[i];\n              }\n            }\n        \"#;\n        validate_reports(src, 1);\n\n        let src = r#\"\n            template Test(n) {\n              signal input a;\n              signal input b;\n              signal output c;\n\n              component check = IsZero();\n              check.in <== b;\n              check.out === 1 - 1;\n\n              c <-- a / b;\n              c * b === a;\n            }\n        \"#;\n        validate_reports(src, 0);\n\n        let src = r#\"\n            template Test(n) {\n              signal input a;\n              signal input b;\n              signal output c;\n\n              c <-- a / b;\n              c * b === a;\n\n              component check = IsZero();\n              check.in <== b;\n              check.out === 0;\n            }\n        \"#;\n        validate_reports(src, 0);\n\n        let src = r#\"\n            template Test(n) {\n              signal input a;\n              signal input b;\n              signal output c;\n\n              c <-- a / (2 * n + 1);\n              c * b === a;\n            }\n        \"#;\n        validate_reports(src, 0);\n\n        let src = r#\"\n            template Test(n) {\n              signal input a;\n              signal input b;\n              signal output c;\n\n              component check = IsZero();\n              check.in <== b;\n              check.out === 1;\n\n              c <-- a / b;\n              c * b === a;\n            }\n        \"#;\n        validate_reports(src, 1);\n\n        let src = r#\"\n            template Test(n) {\n              signal input a;\n              signal input b;\n              signal output c;\n\n              component check = IsZero();\n              check.in <== a;\n              check.out === 0;\n\n              c <-- a / b;\n              c * b === a;\n            }\n        \"#;\n        validate_reports(src, 1);\n    }\n\n    fn validate_reports(src: &str, expected_len: usize) {\n        // Build CFG.\n        let mut reports = ReportCollection::new();\n        let cfg = parse_definition(src)\n            .unwrap()\n            .into_cfg(&Curve::default(), &mut reports)\n            .unwrap()\n            .into_ssa()\n            .unwrap();\n        assert!(reports.is_empty());\n\n        // Generate report collection.\n        let reports = find_unconstrained_division(&cfg);\n        assert_eq!(reports.len(), expected_len);\n    }\n}\n"
  },
  {
    "path": "program_analysis/src/unconstrained_less_than.rs",
    "content": "use std::collections::HashMap;\nuse std::fmt;\n\nuse log::{debug, trace};\n\nuse num_bigint::BigInt;\nuse program_structure::cfg::Cfg;\nuse program_structure::ir::value_meta::{ValueMeta, ValueReduction};\nuse program_structure::report_code::ReportCode;\nuse program_structure::report::{Report, ReportCollection};\nuse program_structure::ir::*;\n\npub struct UnconstrainedLessThanWarning {\n    value: Expression,\n    bit_sizes: Vec<(Meta, Expression)>,\n}\nimpl UnconstrainedLessThanWarning {\n    fn primary_meta(&self) -> &Meta {\n        self.value.meta()\n    }\n\n    pub fn into_report(self) -> Report {\n        let mut report = Report::warning(\n            \"Inputs to `LessThan` need to be constrained to ensure that they are non-negative\"\n                .to_string(),\n            ReportCode::UnconstrainedLessThan,\n        );\n        if let Some(file_id) = self.primary_meta().file_id {\n            report.add_primary(\n                self.primary_meta().file_location(),\n                file_id,\n                format!(\"`{}` needs to be constrained to ensure that it is <= p/2.\", self.value),\n            );\n            for (meta, size) in self.bit_sizes {\n                report.add_secondary(\n                    meta.file_location(),\n                    file_id,\n                    Some(format!(\"`{}` is constrained to `{}` bits here.\", self.value, size)),\n                );\n            }\n        }\n        report\n    }\n}\n\n#[derive(Eq, PartialEq, Hash)]\nstruct VariableAccess {\n    pub var: VariableName,\n    pub access: Vec<AccessType>,\n}\n\nimpl VariableAccess {\n    fn new(var: &VariableName, access: &[AccessType]) -> Self {\n        // We disregard the version to make sure accesses are not order dependent.\n        VariableAccess { var: var.without_version(), access: access.to_vec() }\n    }\n}\n\n/// Tracks component instantiations `var = T(...)` where then template `T` is\n/// either `LessThan` or `Num2Bits`.\nenum Component {\n    LessThan,\n    Num2Bits { bit_size: Box<Expression> },\n}\n\nimpl Component {\n    fn less_than() -> Self {\n        Self::LessThan\n    }\n\n    fn num_2_bits(bit_size: &Expression) -> Self {\n        Self::Num2Bits { bit_size: Box::new(bit_size.clone()) }\n    }\n}\n\n/// Tracks component input signal initializations on the form `T.in <== input`\n/// where `T` is either `LessThan` or `Num2Bits`.\nenum ComponentInput {\n    LessThan { value: Box<Expression> },\n    Num2Bits { value: Box<Expression>, bit_size: Box<Expression> },\n}\n\nimpl ComponentInput {\n    fn less_than(value: &Expression) -> Self {\n        Self::LessThan { value: Box::new(value.clone()) }\n    }\n\n    fn num_2_bits(value: &Expression, bit_size: &Expression) -> Self {\n        Self::Num2Bits { value: Box::new(value.clone()), bit_size: Box::new(bit_size.clone()) }\n    }\n}\n\n/// Tracks constraints for a single input to `LessThan`.\n#[derive(Default)]\nstruct ConstraintData {\n    /// Input to `LessThan`.\n    pub less_than: Vec<Meta>,\n    /// Input to `Num2Bits`.\n    pub num_2_bits: Vec<Meta>,\n    /// Size constraints enforced by `Num2Bits`.\n    pub bit_sizes: Vec<Expression>,\n}\n\n/// The `LessThan` template from Circomlib does not constrain the individual\n/// inputs to the input size `n` bits, or to be positive. If the inputs are\n/// allowed to be greater than p/2 it is possible to find field elements `a` and\n/// `b` such that\n///\n///   1. `a > b` either as unsigned integers, or as signed elements in GF(p),\n///   2. lt = LessThan(n),\n///   3. lt.in[0] = a,\n///   4. lt.in[1] = b, and\n///   5. lt.out = 1\n///\n/// This analysis pass looks for instantiations of `LessThan` where the inputs\n/// are not constrained to be <= p/2 using `Num2Bits`.\npub fn find_unconstrained_less_than(cfg: &Cfg) -> ReportCollection {\n    debug!(\"running unconstrained less-than analysis pass\");\n    let mut components = HashMap::new();\n    for basic_block in cfg.iter() {\n        for stmt in basic_block.iter() {\n            update_components(stmt, &mut components);\n        }\n    }\n    let mut inputs = Vec::new();\n    for basic_block in cfg.iter() {\n        for stmt in basic_block.iter() {\n            update_inputs(stmt, &components, &mut inputs);\n        }\n    }\n    let mut constraints = HashMap::<Expression, ConstraintData>::new();\n    for input in inputs {\n        match input {\n            ComponentInput::LessThan { value } => {\n                let entry = constraints.entry(*value.clone()).or_default();\n                entry.less_than.push(value.meta().clone());\n            }\n            ComponentInput::Num2Bits { value, bit_size, .. } => {\n                let entry = constraints.entry(*value.clone()).or_default();\n                entry.num_2_bits.push(value.meta().clone());\n                entry.bit_sizes.push(*bit_size.clone());\n            }\n        }\n    }\n\n    // Generate a report for each input to `LessThan` where the input size is\n    // not constrained to be positive using `Num2Bits`.\n    let mut reports = ReportCollection::new();\n    let max_value = BigInt::from(cfg.constants().prime_size() - 1);\n    for (value, data) in constraints {\n        // Check if the the value is used as input for `LessThan`.\n        if data.less_than.is_empty() {\n            continue;\n        }\n        // Check if the value is constrained to be positive.\n        let mut is_positive = false;\n        for bit_size in &data.bit_sizes {\n            if let Some(ValueReduction::FieldElement { value }) = bit_size.value() {\n                if value < &max_value {\n                    is_positive = true;\n                    break;\n                }\n            }\n        }\n        if is_positive {\n            continue;\n        }\n        // We failed to prove that the input is positive. Generate a report.\n        reports.push(build_report(&value, &data));\n    }\n    debug!(\"{} new reports generated\", reports.len());\n    reports\n}\n\nfn update_components(stmt: &Statement, components: &mut HashMap<VariableAccess, Component>) {\n    use AssignOp::*;\n    use Statement::*;\n    use Expression::*;\n    if let Substitution { meta, var, op: AssignLocalOrComponent, rhe, .. } = stmt {\n        // If the variable `var` is declared as a local variable or signal, we exit early.\n        if meta.type_knowledge().is_local() || meta.type_knowledge().is_signal() {\n            return;\n        }\n        // If this is an assignment on the form `var[i] = T(...)` we need to store the access and obtain the RHS.\n        let (rhe, access) = if let Update { access, rhe, .. } = rhe {\n            (rhe.as_ref(), access.clone())\n        } else {\n            (rhe, Vec::new())\n        };\n        if let Call { name: component_name, args, .. } = rhe {\n            if component_name == \"LessThan\" && args.len() == 1 {\n                // We assume this is the `LessThan` circuit from Circomlib.\n                trace!(\n                    \"`LessThan` template instantiation `{var}{}` found\",\n                    vec_to_display(&access, \"\")\n                );\n                let component = VariableAccess::new(var, &access);\n                components.insert(component, Component::less_than());\n            } else if component_name == \"Num2Bits\" && args.len() == 1 {\n                // We assume this is the `Num2Bits` circuit from Circomlib.\n                trace!(\n                    \"`LessThan` template instantiation `{var}{}` found\",\n                    vec_to_display(&access, \"\")\n                );\n                let component = VariableAccess::new(var, &access);\n                components.insert(component, Component::num_2_bits(&args[0]));\n            }\n        }\n    }\n}\n\nfn update_inputs(\n    stmt: &Statement,\n    components: &HashMap<VariableAccess, Component>,\n    inputs: &mut Vec<ComponentInput>,\n) {\n    use AssignOp::*;\n    use Statement::*;\n    use Expression::*;\n    use AccessType::*;\n    if let Substitution {\n        var, op: AssignConstraintSignal, rhe: Update { access, rhe, .. }, ..\n    } = stmt\n    {\n        // If this is a `Num2Bits` input signal assignment, the input signal\n        // access would be the last element of the `access` vector.\n        let mut component_access = access.clone();\n        let signal_access = component_access.pop();\n        let component = VariableAccess::new(var, &component_access);\n        if let Some(Component::Num2Bits { bit_size, .. }) = components.get(&component) {\n            let Some(ComponentAccess(signal_name)) = signal_access else {\n                return;\n            };\n            if signal_name != \"in\" {\n                return;\n            }\n            trace!(\"`Num2Bits` input signal assignment `{rhe}` found\");\n            inputs.push(ComponentInput::num_2_bits(rhe, bit_size));\n        }\n\n        // If this is a `LessThan` input signal assignment, the input index\n        // access would be the last element, and the input signal access\n        // would be the next to last element of the `access` vector.\n        let mut component_access = access.clone();\n        let index_access = component_access.pop();\n        let signal_access = component_access.pop();\n        let component = VariableAccess::new(var, &component_access);\n        if let Some(Component::LessThan { .. }) = components.get(&component) {\n            let (Some(ComponentAccess(signal_name)), Some(ArrayAccess(_))) =\n                (signal_access, index_access)\n            else {\n                return;\n            };\n            if signal_name != \"in\" {\n                return;\n            }\n            trace!(\"`LessThan` input signal assignment `{rhe}` found\");\n            inputs.push(ComponentInput::less_than(rhe));\n        }\n    }\n}\n\n#[must_use]\nfn build_report(value: &Expression, data: &ConstraintData) -> Report {\n    UnconstrainedLessThanWarning {\n        value: value.clone(),\n        bit_sizes: data.num_2_bits.iter().cloned().zip(data.bit_sizes.iter().cloned()).collect(),\n    }\n    .into_report()\n}\n\n#[must_use]\nfn vec_to_display<T: fmt::Display>(elems: &[T], sep: &str) -> String {\n    elems.iter().map(|elem| format!(\"{elem}\")).collect::<Vec<String>>().join(sep)\n}\n\n#[cfg(test)]\nmod tests {\n    use parser::parse_definition;\n    use program_structure::{cfg::IntoCfg, constants::Curve};\n\n    use super::*;\n\n    #[test]\n    fn test_unconstrained_less_than() {\n        let src = r#\"\n            template Test(n) {\n              signal input small;\n              signal input large;\n              signal output ok;\n\n              // Check that small < large.\n              component lt = LessThan(n);\n              lt.in[0] <== small;\n              lt.in[1] <== large;\n\n              ok <== lt.out;\n            }\n        \"#;\n        validate_reports(src, 2);\n\n        let src = r#\"\n            template Test(n) {\n              signal input small;\n              signal input large;\n              signal output ok;\n\n              // Constrain inputs to n bits.\n              component n2b[2];\n              n2b[0] = Num2Bits(n);\n              n2b[0].in <== small;\n              n2b[1] = Num2Bits(n + 1);\n              n2b[1].in <== large;\n\n              // Check that small < large.\n              component lt = LessThan(n);\n              lt.in[0] <== small;\n              lt.in[1] <== large;\n\n              ok <== lt.out;\n            }\n        \"#;\n        validate_reports(src, 2);\n\n        let src = r#\"\n            template Test(n) {\n              signal input small;\n              signal input large;\n              signal output ok;\n\n              // Constrain inputs to n bits.\n              component n2b[2];\n              n2b[0] = Num2Bits(n);\n              n2b[0].in <== small;\n              n2b[1] = Num2Bits(32);\n              n2b[1].in <== large;\n\n              // Check that small < large.\n              component lt = LessThan(n);\n              lt.in[0] <== small;\n              lt.in[1] <== large;\n\n              ok <== lt.out;\n            }\n        \"#;\n        validate_reports(src, 1);\n\n        let src = r#\"\n            template Test(n) {\n              signal input small;\n              signal input large;\n              signal output ok;\n\n              // Check that small < large.\n              component lt = LessThan(n);\n              lt.in[1] <== large;\n              lt.in[0] <== small;\n\n              // Constrain inputs to n bits.\n              component n2b[2];\n              n2b[0] = Num2Bits(32);\n              n2b[0].in <== small;\n              n2b[1] = Num2Bits(64);\n              n2b[1].in <== large;\n\n              ok <== lt.out;\n            }\n        \"#;\n        validate_reports(src, 0);\n    }\n\n    fn validate_reports(src: &str, expected_len: usize) {\n        // Build CFG.\n        let mut reports = ReportCollection::new();\n        let cfg = parse_definition(src)\n            .unwrap()\n            .into_cfg(&Curve::default(), &mut reports)\n            .unwrap()\n            .into_ssa()\n            .unwrap();\n        assert!(reports.is_empty());\n\n        // Generate report collection.\n        let reports = find_unconstrained_less_than(&cfg);\n        assert_eq!(reports.len(), expected_len);\n    }\n}\n"
  },
  {
    "path": "program_analysis/src/under_constrained_signals.rs",
    "content": "use std::collections::{HashMap, HashSet};\n\nuse log::debug;\nuse program_structure::cfg::Cfg;\nuse program_structure::file_definition::{FileID, FileLocation};\nuse program_structure::intermediate_representation::variable_meta::VariableMeta;\nuse program_structure::report::{ReportCollection, Report};\nuse program_structure::ir::*;\nuse program_structure::report_code::ReportCode;\n\nuse crate::taint_analysis::{run_taint_analysis, TaintAnalysis};\n\nconst MIN_CONSTRAINT_COUNT: usize = 2;\n\n#[derive(PartialEq, Eq, Hash)]\nenum ConstraintLocation {\n    Ordinary(FileLocation),\n    Loop,\n}\n\nimpl ConstraintLocation {\n    fn file_location(&self) -> Option<FileLocation> {\n        use ConstraintLocation::*;\n        match self {\n            Ordinary(file_location) => Some(file_location.clone()),\n            Loop => None,\n        }\n    }\n}\n\ntype ConstraintLocations = HashMap<VariableName, Vec<ConstraintLocation>>;\n\npub struct UnderConstrainedSignalWarning {\n    name: VariableName,\n    dimensions: Vec<Expression>,\n    file_id: Option<FileID>,\n    primary_location: FileLocation,\n    secondary_location: Option<FileLocation>,\n}\n\nimpl UnderConstrainedSignalWarning {\n    pub fn into_report(self) -> Report {\n        let mut report = Report::warning(\n            \"Intermediate signals should typically occur in at least two separate constraints.\"\n                .to_string(),\n            ReportCode::UnderConstrainedSignal,\n        );\n        if let Some(file_id) = self.file_id {\n            if self.dimensions.is_empty() {\n                report.add_primary(\n                    self.primary_location,\n                    file_id,\n                    format!(\"The intermediate signal `{}` is declared here.\", self.name),\n                );\n                if let Some(secondary_location) = self.secondary_location {\n                    report.add_secondary(\n                        secondary_location,\n                        file_id,\n                        Some(format!(\n                            \"The intermediate signal `{}` is constrained here.\",\n                            self.name\n                        )),\n                    );\n                }\n            } else {\n                report.add_primary(\n                    self.primary_location,\n                    file_id,\n                    format!(\"The intermediate signal array `{}` is declared here.\", self.name),\n                );\n                if let Some(secondary_location) = self.secondary_location {\n                    report.add_secondary(\n                        secondary_location,\n                        file_id,\n                        Some(format!(\n                            \"The intermediate signals in `{}` are constrained here.\",\n                            self.name\n                        )),\n                    );\n                }\n            }\n        }\n        report\n    }\n}\n\n// Intermediate signals should occur in at least two separate constraints. One\n// to define the value of the signal and one to constrain an input or output\n// signal.\npub fn find_under_constrained_signals(cfg: &Cfg) -> ReportCollection {\n    debug!(\"running under-constrained signals analysis pass\");\n\n    // Run taint analysis to be able to track data flow.\n    let taint_analysis = run_taint_analysis(cfg);\n\n    // Compute the set of intermediate signals.\n    let mut constraint_locations = cfg\n        .variables()\n        .filter_map(|name| {\n            if matches!(cfg.get_type(name), Some(VariableType::Signal(SignalType::Intermediate, _)))\n            {\n                Some((name.clone(), Vec::new()))\n            } else {\n                None\n            }\n        })\n        .collect::<ConstraintLocations>();\n\n    // Iterate through the CFG to identify intermediate signal constraints.\n    for basic_block in cfg.iter() {\n        for stmt in basic_block.iter() {\n            visit_statement(\n                stmt,\n                basic_block.in_loop(),\n                &taint_analysis,\n                &mut constraint_locations,\n            );\n        }\n    }\n\n    // Generate reports.\n    let mut reports = ReportCollection::new();\n    for (signal, locations) in constraint_locations {\n        if locations.len() < MIN_CONSTRAINT_COUNT && !locations.contains(&ConstraintLocation::Loop)\n        {\n            let secondary_location =\n                locations.first().and_then(|location| location.file_location());\n            if let Some(declaration) = cfg.get_declaration(&signal) {\n                reports.push(build_report(\n                    &signal,\n                    declaration.dimensions(),\n                    declaration.file_id(),\n                    declaration.file_location(),\n                    secondary_location,\n                ))\n            }\n        }\n    }\n    debug!(\"{} new reports generated\", reports.len());\n    reports\n}\n\nfn visit_statement(\n    stmt: &Statement,\n    in_loop: bool,\n    taint_analysis: &TaintAnalysis,\n    constraint_counts: &mut ConstraintLocations,\n) {\n    use AssignOp::*;\n    use Statement::*;\n    match stmt {\n        // Update the constraint count for each intermediate signal. If the\n        // statement occurs in a loop, we consider the minimum count to be\n        // reached immediately.\n        Substitution { meta, op: AssignConstraintSignal, .. } | ConstraintEquality { meta, .. } => {\n            let sinks = stmt.variables_used().map(|var| var.name().clone()).collect::<HashSet<_>>();\n            for (source, locations) in constraint_counts.iter_mut() {\n                if taint_analysis.taints_any(source, &sinks) {\n                    if in_loop {\n                        locations.push(ConstraintLocation::Loop);\n                    } else {\n                        locations.push(ConstraintLocation::Ordinary(meta.file_location()))\n                    }\n                }\n            }\n        }\n        _ => {}\n    }\n}\n\nfn build_report(\n    signal: &VariableName,\n    dimensions: &[Expression],\n    file_id: Option<FileID>,\n    primary_location: FileLocation,\n    secondary_location: Option<FileLocation>,\n) -> Report {\n    UnderConstrainedSignalWarning {\n        name: signal.clone(),\n        dimensions: dimensions.to_vec(),\n        file_id,\n        primary_location,\n        secondary_location,\n    }\n    .into_report()\n}\n\n#[cfg(test)]\nmod tests {\n    use parser::parse_definition;\n    use program_structure::{cfg::IntoCfg, constants::Curve};\n\n    use super::*;\n\n    #[test]\n    fn test_under_constrained_signals() {\n        let src = r#\"\n            template Test(n) {\n              signal input a;\n              signal b;\n              signal output c;\n\n              c <== 2 * a;\n            }\n        \"#;\n        validate_reports(src, 1);\n\n        let src = r#\"\n            template Test(n) {\n              signal input a;\n              signal b;\n              signal output c;\n\n              c <== a * b;\n            }\n        \"#;\n        validate_reports(src, 1);\n\n        let src = r#\"\n            template Test(n) {\n              signal input a;\n              signal b;\n              signal output c;\n\n              b <== a * a;\n              c <== a * b;\n            }\n        \"#;\n        validate_reports(src, 0);\n\n        let src = r#\"\n            template Test(n) {\n              signal input a[2];\n              signal b;\n              signal output c;\n\n              var d = 2 * b;\n              a[0] === d;\n              a[1] === b + 1;\n              c <== a[0] + a[1];\n            }\n        \"#;\n        validate_reports(src, 0);\n\n        let src = r#\"\n            template Test(n) {\n              signal input a[2];\n              signal b[2];\n              signal output c;\n\n              for (var i = 0; i < 2; i++) {\n                b[i] <== a[i];\n              }\n              c <== a[0] + a[1];\n            }\n        \"#;\n        validate_reports(src, 0);\n    }\n\n    fn validate_reports(src: &str, expected_len: usize) {\n        // Build CFG.\n        let mut reports = ReportCollection::new();\n        let cfg = parse_definition(src)\n            .unwrap()\n            .into_cfg(&Curve::default(), &mut reports)\n            .unwrap()\n            .into_ssa()\n            .unwrap();\n        assert!(reports.is_empty());\n\n        // Generate report collection.\n        let reports = find_under_constrained_signals(&cfg);\n        assert_eq!(reports.len(), expected_len);\n    }\n}\n"
  },
  {
    "path": "program_analysis/src/unused_output_signal.rs",
    "content": "use log::debug;\nuse std::collections::HashSet;\n\nuse program_structure::{\n    ir::*,\n    ir::value_meta::ValueMeta,\n    report_code::ReportCode,\n    cfg::{Cfg, DefinitionType},\n    report::{Report, ReportCollection},\n    file_definition::{FileID, FileLocation},\n};\n\nuse crate::analysis_context::AnalysisContext;\n\n// Known templates that are commonly instantiated without accessing the\n// corresponding output signals.\nconst ALLOW_LIST: [&str; 1] = [\"Num2Bits\"];\n\nstruct UnusedOutputSignalWarning {\n    // Location of template instantiation.\n    file_id: Option<FileID>,\n    file_location: FileLocation,\n    // The currently analyzed template.\n    current_template: String,\n    // The instantiated template with an unused output signal.\n    component_template: String,\n    // The name of the unused output signal.\n    signal_name: String,\n}\n\nimpl UnusedOutputSignalWarning {\n    pub fn into_report(self) -> Report {\n        let mut report = Report::warning(\n            format!(\n                \"The output signal `{}` defined by the template `{}` is not constrained in `{}`.\",\n                self.signal_name, self.component_template, self.current_template\n            ),\n            ReportCode::UnusedOutputSignal,\n        );\n        if let Some(file_id) = self.file_id {\n            report.add_primary(\n                self.file_location,\n                file_id,\n                format!(\"The template `{}` is instantiated here.\", self.component_template),\n            );\n        }\n        report\n    }\n}\n\n#[derive(Clone, Debug)]\nstruct VariableAccess {\n    pub var: VariableName,\n    pub access: Vec<AccessType>,\n}\n\nimpl VariableAccess {\n    fn new(var: &VariableName, access: &[AccessType]) -> Self {\n        // We disregard the version to make sure accesses are not order dependent.\n        VariableAccess { var: var.without_version(), access: access.to_vec() }\n    }\n}\n\n/// A reflexive and symmetric relation capturing partial information about\n/// equality.\ntrait MaybeEqual {\n    fn maybe_equal(&self, other: &Self) -> bool;\n}\n\n/// This is a reflexive and symmetric (but not transitive!) relation\n/// identifying all array accesses where the indices are not explicitly known\n/// to be different (e.g. from constant propagation). The relation is not\n/// transitive since `v[0] == v[i]` and `v[i] == v[1]`, but `v[0] != v[1]`.\n///\n/// Since `maybe_equal` is not transitive we cannot use it to define\n/// `PartialEq` for `VariableAccess`. This also means that we cannot use hash\n/// sets or hash maps to track variable accesses using this as our equality\n/// relation.\nimpl MaybeEqual for VariableAccess {\n    fn maybe_equal(&self, other: &VariableAccess) -> bool {\n        use AccessType::*;\n        if self.var.name() != other.var.name() {\n            return false;\n        }\n        if self.access.len() != other.access.len() {\n            return false;\n        }\n        for (self_access, other_access) in self.access.iter().zip(other.access.iter()) {\n            match (self_access, other_access) {\n                (ArrayAccess(_), ComponentAccess(_)) => {\n                    return false;\n                }\n                (ComponentAccess(_), ArrayAccess(_)) => {\n                    return false;\n                }\n                (ComponentAccess(self_name), ComponentAccess(other_name))\n                    if self_name != other_name =>\n                {\n                    return false;\n                }\n                (ArrayAccess(self_index), ArrayAccess(other_index)) => {\n                    match (self_index.value(), other_index.value()) {\n                        (Some(self_value), Some(other_value)) if self_value != other_value => {\n                            return false;\n                        }\n                        // Identify all other array accesses.\n                        _ => {}\n                    }\n                }\n                // Identify all array accesses.\n                _ => {}\n            }\n        }\n        true\n    }\n}\n\n/// A relation capturing partial information about containment.\ntrait MaybeContains<T> {\n    fn maybe_contains(&self, element: &T) -> bool;\n}\n\nimpl<T> MaybeContains<T> for Vec<T>\nwhere\n    T: MaybeEqual,\n{\n    fn maybe_contains(&self, element: &T) -> bool {\n        self.iter().any(|item| item.maybe_equal(element))\n    }\n}\n\nstruct ComponentData {\n    pub meta: Meta,\n    pub var_name: VariableName,\n    pub var_access: Vec<AccessType>,\n    pub template_name: String,\n}\n\nimpl ComponentData {\n    pub fn new(\n        meta: &Meta,\n        var_name: &VariableName,\n        var_access: &[AccessType],\n        template_name: &str,\n    ) -> Self {\n        ComponentData {\n            meta: meta.clone(),\n            var_name: var_name.clone(),\n            var_access: var_access.to_vec(),\n            template_name: template_name.to_string(),\n        }\n    }\n}\n\nstruct SignalData {\n    pub meta: Meta,\n    pub template_name: String,\n    pub signal_name: String,\n    pub signal_access: VariableAccess,\n}\n\nimpl SignalData {\n    pub fn new(\n        meta: &Meta,\n        template_name: &str,\n        signal_name: &str,\n        signal_access: VariableAccess,\n    ) -> SignalData {\n        SignalData {\n            meta: meta.clone(),\n            template_name: template_name.to_string(),\n            signal_name: signal_name.to_string(),\n            signal_access,\n        }\n    }\n}\n\npub fn find_unused_output_signals(\n    context: &mut dyn AnalysisContext,\n    current_cfg: &Cfg,\n) -> ReportCollection {\n    // Exit early if the given CFG represents a function.\n    if matches!(current_cfg.definition_type(), DefinitionType::Function) {\n        return ReportCollection::new();\n    }\n    debug!(\"running unused output signal analysis pass\");\n    let allow_list = HashSet::from(ALLOW_LIST);\n\n    // Collect all instantiated components.\n    let mut components = Vec::new();\n    let mut accesses = Vec::new();\n    for basic_block in current_cfg.iter() {\n        for stmt in basic_block.iter() {\n            visit_statement(stmt, current_cfg, &mut components, &mut accesses);\n        }\n    }\n    let mut output_signals = Vec::new();\n    for component in components {\n        // Ignore templates on the allow list.\n        if allow_list.contains(&component.template_name[..]) {\n            continue;\n        }\n        if let Ok(component_cfg) = context.template(&component.template_name) {\n            for output_signal in component_cfg.output_signals() {\n                if let Some(declaration) = component_cfg.get_declaration(output_signal) {\n                    // The signal access pattern is given by the component\n                    // access pattern, followed by the output signal name,\n                    // followed by an array access corresponding to each\n                    // dimension entry for the signal.\n                    //\n                    // E.g., for the component `c[i]` with an output signal\n                    // `out` which is a double array, we get `c[i].out[j][k]`.\n                    // Since we identify array accesses we simply use `i` for\n                    // each array access corresponding to the dimensions of the\n                    // signal.\n                    let mut var_access = component.var_access.clone();\n                    var_access.push(AccessType::ComponentAccess(output_signal.name().to_string()));\n                    for _ in declaration.dimensions() {\n                        let meta = Meta::new(&(0..0), &None);\n                        let index =\n                            Expression::Variable { meta, name: VariableName::from_string(\"i\") };\n                        var_access.push(AccessType::ArrayAccess(Box::new(index)));\n                    }\n                    let signal_access = VariableAccess::new(&component.var_name, &var_access);\n                    output_signals.push(SignalData::new(\n                        &component.meta,\n                        &component.template_name,\n                        output_signal.name(),\n                        signal_access,\n                    ));\n                }\n            }\n        }\n    }\n    let mut reports = ReportCollection::new();\n    for output_signal in output_signals {\n        if !maybe_accesses(&accesses, &output_signal.signal_access) {\n            reports.push(build_report(\n                &output_signal.meta,\n                current_cfg.name(),\n                &output_signal.template_name,\n                &output_signal.signal_name,\n            ))\n        }\n    }\n\n    debug!(\"{} new reports generated\", reports.len());\n    reports\n}\n\n// Check if there is an access to a prefix of the output signal access which\n// contains the output signal name. E.g. for the output signal `n2b[1].out[0]`\n// it is enough that the list of all variable accesses `maybe_contains` the\n// prefix `n2b[1].out`. This is to catch instances where the template passes the\n// output signal as input to a function.\nfn maybe_accesses(accesses: &Vec<VariableAccess>, signal_access: &VariableAccess) -> bool {\n    use AccessType::*;\n    let mut signal_access = signal_access.clone();\n    while !accesses.maybe_contains(&signal_access) {\n        if let Some(ComponentAccess(_)) = signal_access.access.last() {\n            // The output signal name is the last component access in the access\n            // array. If it is not included in the access, the output signal is\n            // not accessed by the template.\n            return false;\n        } else {\n            signal_access.access.pop();\n        }\n    }\n    true\n}\n\nfn visit_statement(\n    stmt: &Statement,\n    cfg: &Cfg,\n    components: &mut Vec<ComponentData>,\n    accesses: &mut Vec<VariableAccess>,\n) {\n    use Statement::*;\n    use Expression::*;\n    use VariableType::*;\n    // Collect all instantiated components.\n    if let Substitution { var: var_name, rhe, .. } = stmt {\n        let (var_access, rhe) = if let Update { access, rhe, .. } = rhe {\n            (access.clone(), *rhe.clone())\n        } else {\n            (Vec::new(), rhe.clone())\n        };\n        if let (Some(Component), Call { meta, name: template_name, .. }) =\n            (cfg.get_type(var_name), rhe)\n        {\n            components.push(ComponentData::new(&meta, var_name, &var_access, &template_name));\n        }\n    }\n    // Collect all variable accesses.\n    match stmt {\n        Substitution { rhe, .. } => visit_expression(rhe, accesses),\n        ConstraintEquality { lhe, rhe, .. } => {\n            visit_expression(lhe, accesses);\n            visit_expression(rhe, accesses);\n        }\n        Declaration { .. } => { /* We ignore dimensions in declarations. */ }\n        IfThenElse { .. } => { /* We ignore if-statement conditions. */ }\n        Return { .. } => { /* We ignore return statements. */ }\n        LogCall { .. } => { /* We ignore log statements. */ }\n        Assert { .. } => { /* We ignore asserts. */ }\n    }\n}\n\nfn visit_expression(expr: &Expression, accesses: &mut Vec<VariableAccess>) {\n    use Expression::*;\n    match expr {\n        PrefixOp { rhe, .. } => {\n            visit_expression(rhe, accesses);\n        }\n        InfixOp { lhe, rhe, .. } => {\n            visit_expression(lhe, accesses);\n            visit_expression(rhe, accesses);\n        }\n        SwitchOp { cond, if_true, if_false, .. } => {\n            visit_expression(cond, accesses);\n            visit_expression(if_true, accesses);\n            visit_expression(if_false, accesses);\n        }\n        Call { args, .. } => {\n            for arg in args {\n                visit_expression(arg, accesses);\n            }\n        }\n        InlineArray { values, .. } => {\n            for value in values {\n                visit_expression(value, accesses);\n            }\n        }\n        Access { var, access, .. } => {\n            accesses.push(VariableAccess::new(var, access));\n        }\n        Update { rhe, .. } => {\n            // We ignore accesses in assignments.\n            visit_expression(rhe, accesses);\n        }\n        Variable { .. } | Number(_, _) | Phi { .. } => (),\n    }\n}\n\nfn build_report(\n    meta: &Meta,\n    current_template: &str,\n    component_template: &str,\n    signal_name: &str,\n) -> Report {\n    UnusedOutputSignalWarning {\n        file_id: meta.file_id(),\n        file_location: meta.file_location(),\n        current_template: current_template.to_string(),\n        component_template: component_template.to_string(),\n        signal_name: signal_name.to_string(),\n    }\n    .into_report()\n}\n\n#[cfg(test)]\nmod tests {\n    use num_bigint_dig::BigInt;\n    use program_structure::{\n        constants::Curve,\n        intermediate_representation::{\n            VariableName, AccessType, Expression, Meta, value_meta::ValueReduction,\n        },\n    };\n\n    use crate::{\n        analysis_runner::AnalysisRunner,\n        unused_output_signal::{MaybeEqual, MaybeContains, maybe_accesses},\n    };\n\n    use super::{find_unused_output_signals, VariableAccess};\n\n    #[test]\n    fn test_maybe_equal() {\n        use AccessType::*;\n        use Expression::*;\n        use ValueReduction::*;\n\n        let var = VariableName::from_string(\"var\");\n        let meta = Meta::new(&(0..0), &None);\n        let mut zero = Box::new(Number(meta.clone(), BigInt::from(0)));\n        let mut one = Box::new(Number(meta.clone(), BigInt::from(1)));\n        let i = Box::new(Variable { meta, name: VariableName::from_string(\"i\") });\n\n        // Set the value of `zero` and `one` explicitly.\n        let _ = zero\n            .meta_mut()\n            .value_knowledge_mut()\n            .set_reduces_to(FieldElement { value: BigInt::from(0) });\n        let _ = one\n            .meta_mut()\n            .value_knowledge_mut()\n            .set_reduces_to(FieldElement { value: BigInt::from(1) });\n\n        // `var[0].out`\n        let first_access = VariableAccess::new(\n            &var.with_version(1),\n            &[ArrayAccess(zero.clone()), ComponentAccess(\"out\".to_string())],\n        );\n        // `var[i].out`\n        let second_access = VariableAccess::new(\n            &var.with_version(2),\n            &[ArrayAccess(i.clone()), ComponentAccess(\"out\".to_string())],\n        );\n        // `var[1].out`\n        let third_access = VariableAccess::new(\n            &var.with_version(3),\n            &[ArrayAccess(one), ComponentAccess(\"out\".to_string())],\n        );\n        // `var[i].out[0]`\n        let fourth_access = VariableAccess::new(\n            &var.with_version(4),\n            &[ArrayAccess(i), ComponentAccess(\"out\".to_string()), ArrayAccess(zero)],\n        );\n\n        // The first and second accesses should be identified.\n        assert!(first_access.maybe_equal(&second_access));\n        // The first and third accesses should not be identified.\n        assert!(!first_access.maybe_equal(&third_access));\n\n        let accesses = vec![first_access];\n\n        // The first and second accesses should be identified.\n        assert!(accesses.maybe_contains(&second_access));\n        // The first and third accesses should not be identified.\n        assert!(!accesses.maybe_contains(&third_access));\n\n        // The fourth access is not equal to the first, but a prefix is.\n        assert!(!accesses.maybe_contains(&fourth_access));\n        assert!(maybe_accesses(&accesses, &fourth_access));\n    }\n\n    #[test]\n    fn test_maybe_accesses() {}\n\n    #[test]\n    fn test_unused_output_signal() {\n        // The output signal `out` in `Test` is not accessed, for any of the two\n        // instantiated components.\n        let src = [\n            r#\"\n            template Test() {\n                signal input in;\n                signal output out;\n\n                out <== 2 * in + 1;\n            }\n        \"#,\n            r#\"\n            template Main() {\n                signal input in[2];\n\n                component test[2];\n                test[0] = Test();\n                test[1] = Test();\n                test[0].in <== in[0];\n                test[1].in <== in[1];\n            }\n        \"#,\n        ];\n        validate_reports(\"Main\", &src, 2);\n\n        // `Num2Bits` is on the allow list and should not produce a report.\n        let src = [\n            r#\"\n            template Num2Bits(n) {\n                signal input in;\n                signal output out[n];\n\n                for (var i = 0; i < n; i++) {\n                    out[i] <== in;\n                }\n            }\n        \"#,\n            r#\"\n            template Main() {\n                signal input in;\n\n                component n2b = Num2Bits();\n                n2b.in <== in[0];\n\n                in[1] === in[0] + 1;\n            }\n        \"#,\n        ];\n        validate_reports(\"Main\", &src, 0);\n\n        // If the template is not known we should not produce a report.\n        let src = [r#\"\n            template Main() {\n                signal input in[2];\n\n                component test[2];\n                test[0] = Test();\n                test[1] = Test();\n                test[0].in <== in[0];\n                test[1].in <== in[1];\n            }\n        \"#];\n        validate_reports(\"Main\", &src, 0);\n\n        // Should generate a warning for `test[1]` but not for `test[0]`.\n        let src = [\n            r#\"\n            template Test() {\n                signal input in;\n                signal output out;\n\n                out <== 2 * in + 1;\n            }\n        \"#,\n            r#\"\n            template Main() {\n                signal input in[2];\n\n                component test[2];\n                test[0] = Test();\n                test[1] = Test();\n                test[0].in <== in[0];\n                test[1].in <== in[1];\n\n                test[0].out === 1;\n            }\n        \"#,\n        ];\n        validate_reports(\"Main\", &src, 1);\n\n        // Should not generate a warning for `test.out`.\n        let src = [\n            r#\"\n            template Test() {\n                signal input in;\n                signal output out[2];\n\n                out[0] <== 2 * in + 1;\n                out[1] <== 3 * in + 2;\n            }\n        \"#,\n            r#\"\n            template Main() {\n                signal input in;\n\n                component test;\n                test = Test();\n                test.in <== in[0];\n\n                func(test.out) === 1;\n            }\n        \"#,\n        ];\n        validate_reports(\"Main\", &src, 0);\n\n        // TODO: Should detect that `test[i].out[1]` is not accessed.\n        let src = [\n            r#\"\n            template Test() {\n                signal input in;\n                signal output out[2];\n\n                out[0] <== 2 * in + 1;\n                out[1] <== 3 * in + 2;\n            }\n        \"#,\n            r#\"\n            template Main() {\n                signal input in[2];\n\n                component test[2];\n                for (var i = 0; i < 2; i++) {\n                    test[i] = Test();\n                    test[i].in <== in[i];\n                }\n                for (var i = 0; i < 2; i++) {\n                    test[i].out[0] === 1;\n                }\n            }\n        \"#,\n        ];\n        validate_reports(\"Main\", &src, 0);\n\n        // TODO: Should detect that `test[1].out` is not accessed.\n        let src = [\n            r#\"\n            template Test() {\n                signal input in;\n                signal output out;\n\n                out <== 2 * in + 1;\n            }\n        \"#,\n            r#\"\n            template Main() {\n                signal input in[2];\n\n                component test[2];\n                for (var i = 0; i < 2; i++) {\n                    test[i] = Test();\n                    test[i].in = in[i];\n                }\n\n                test[0].out === 1;\n            }\n        \"#,\n        ];\n        validate_reports(\"Main\", &src, 0);\n    }\n\n    fn validate_reports(name: &str, src: &[&str], expected_len: usize) {\n        let mut context = AnalysisRunner::new(Curve::Goldilocks).with_src(src);\n        let cfg = context.take_template(name).unwrap();\n        let reports = find_unused_output_signals(&mut context, &cfg);\n        assert_eq!(reports.len(), expected_len);\n    }\n}\n"
  },
  {
    "path": "program_structure/Cargo.toml",
    "content": "[package]\nname = \"circomspect-program-structure\"\nversion = \"2.1.4\"\nedition = \"2021\"\nrust-version = \"1.65\"\nlicense = \"LGPL-3.0-only\"\ndescription = \"Support crate for the Circomspect static analyzer\"\nrepository = \"https://github.com/trailofbits/circomspect\"\nauthors = [\n  \"hermeGarcia <hermegarcianavarro@gmail.com>\",\n  \"Fredrik Dahlgren <fredrik.dahlgren@trailofbits.com>\",\n]\n\n[dependencies]\nanyhow = \"1.0\"\natty = \"0.2\"\ncircom_algebra = { package = \"circomspect-circom-algebra\", version = \"2.0.2\", path = \"../circom_algebra\" }\ncodespan = \"0.11\"\ncodespan-reporting = \"0.11\"\nlog = \"0.4\"\nregex = \"1.7\"\nrustc-hex = \"2.1\"\nnum-bigint-dig = \"0.8\"\nnum-traits = \"0.2\"\nserde = \"1.0\"\nserde_derive = \"1.0\"\nserde-sarif = \"0.4\"\nserde_json = \"1.0\"\nthiserror = \"1.0\"\ntermcolor = \"1.1.3\"\n\n[dev-dependencies]\nproptest = \"1.1\"\ncircom_algebra = { package = \"circomspect-circom-algebra\", version = \"2.0.2\", path = \"../circom_algebra\" }\n"
  },
  {
    "path": "program_structure/src/abstract_syntax_tree/assign_op_impl.rs",
    "content": "use super::ast::AssignOp;\n\nimpl AssignOp {\n    pub fn is_signal_operator(self) -> bool {\n        use AssignOp::*;\n        matches!(self, AssignConstraintSignal | AssignSignal)\n    }\n}\n"
  },
  {
    "path": "program_structure/src/abstract_syntax_tree/ast.rs",
    "content": "use crate::file_definition::FileLocation;\nuse num_bigint::BigInt;\nuse serde_derive::{Deserialize, Serialize};\n\npub trait FillMeta {\n    fn fill(&mut self, file_id: usize, elem_id: &mut usize);\n}\n\npub type MainComponent = (Vec<String>, Expression);\n\npub fn build_main_component(public: Vec<String>, call: Expression) -> MainComponent {\n    (public, call)\n}\n\npub type Version = (usize, usize, usize);\npub type TagList = Vec<String>;\n\n#[derive(Clone)]\npub struct Include {\n    pub meta: Meta,\n    pub path: String,\n}\n\npub fn build_include(meta: Meta, path: String) -> Include {\n    Include { meta, path }\n}\n\n#[derive(Clone)]\npub struct Meta {\n    pub elem_id: usize,\n    pub start: usize,\n    pub end: usize,\n    pub location: FileLocation,\n    pub file_id: Option<usize>,\n    pub component_inference: Option<String>,\n    type_knowledge: TypeKnowledge,\n    memory_knowledge: MemoryKnowledge,\n}\nimpl Meta {\n    pub fn new(start: usize, end: usize) -> Meta {\n        Meta {\n            end,\n            start,\n            elem_id: 0,\n            location: start..end,\n            file_id: Option::None,\n            component_inference: None,\n            type_knowledge: TypeKnowledge::default(),\n            memory_knowledge: MemoryKnowledge::default(),\n        }\n    }\n    pub fn change_location(&mut self, location: FileLocation, file_id: Option<usize>) {\n        self.location = location;\n        self.file_id = file_id;\n    }\n    pub fn get_start(&self) -> usize {\n        self.location.start\n    }\n    pub fn get_end(&self) -> usize {\n        self.location.end\n    }\n    pub fn get_file_id(&self) -> usize {\n        if let Option::Some(id) = self.file_id {\n            id\n        } else {\n            panic!(\"Empty file id accessed\")\n        }\n    }\n    pub fn get_memory_knowledge(&self) -> &MemoryKnowledge {\n        &self.memory_knowledge\n    }\n    pub fn get_type_knowledge(&self) -> &TypeKnowledge {\n        &self.type_knowledge\n    }\n    pub fn get_mut_memory_knowledge(&mut self) -> &mut MemoryKnowledge {\n        &mut self.memory_knowledge\n    }\n    pub fn get_mut_type_knowledge(&mut self) -> &mut TypeKnowledge {\n        &mut self.type_knowledge\n    }\n    pub fn file_location(&self) -> FileLocation {\n        self.location.clone()\n    }\n    pub fn set_file_id(&mut self, file_id: usize) {\n        self.file_id = Option::Some(file_id);\n    }\n}\n\n#[derive(Clone)]\npub struct AST {\n    pub meta: Meta,\n    pub compiler_version: Option<Version>,\n    pub custom_gates: bool,\n    pub custom_gates_declared: bool,\n    pub includes: Vec<Include>,\n    pub definitions: Vec<Definition>,\n    pub main_component: Option<MainComponent>,\n}\nimpl AST {\n    pub fn new(\n        meta: Meta,\n        compiler_version: Option<Version>,\n        custom_gates: bool,\n        includes: Vec<Include>,\n        definitions: Vec<Definition>,\n        main_component: Option<MainComponent>,\n    ) -> AST {\n        let custom_gates_declared = definitions.iter().any(|definition| {\n            matches!(definition, Definition::Template { is_custom_gate: true, .. })\n        });\n        AST {\n            meta,\n            compiler_version,\n            custom_gates,\n            custom_gates_declared,\n            includes,\n            definitions,\n            main_component,\n        }\n    }\n}\n\n#[derive(Clone)]\npub enum Definition {\n    Template {\n        meta: Meta,\n        name: String,\n        args: Vec<String>,\n        arg_location: FileLocation,\n        body: Statement,\n        parallel: bool,\n        is_custom_gate: bool,\n    },\n    Function {\n        meta: Meta,\n        name: String,\n        args: Vec<String>,\n        arg_location: FileLocation,\n        body: Statement,\n    },\n}\npub fn build_template(\n    meta: Meta,\n    name: String,\n    args: Vec<String>,\n    arg_location: FileLocation,\n    body: Statement,\n    parallel: bool,\n    is_custom_gate: bool,\n) -> Definition {\n    Definition::Template { meta, name, args, arg_location, body, parallel, is_custom_gate }\n}\n\npub fn build_function(\n    meta: Meta,\n    name: String,\n    args: Vec<String>,\n    arg_location: FileLocation,\n    body: Statement,\n) -> Definition {\n    Definition::Function { meta, name, args, arg_location, body }\n}\n\nimpl Definition {\n    pub fn name(&self) -> String {\n        match self {\n            Self::Template { name, .. } => name.clone(),\n            Self::Function { name, .. } => name.clone(),\n        }\n    }\n}\n\n#[derive(Clone)]\npub enum Statement {\n    IfThenElse {\n        meta: Meta,\n        cond: Expression,\n        if_case: Box<Statement>,\n        else_case: Option<Box<Statement>>,\n    },\n    While {\n        meta: Meta,\n        cond: Expression,\n        stmt: Box<Statement>,\n    },\n    Return {\n        meta: Meta,\n        value: Expression,\n    },\n    InitializationBlock {\n        meta: Meta,\n        xtype: VariableType,\n        initializations: Vec<Statement>,\n    },\n    Declaration {\n        meta: Meta,\n        xtype: VariableType,\n        name: String,\n        dimensions: Vec<Expression>,\n        is_constant: bool,\n    },\n    Substitution {\n        meta: Meta,\n        var: String,\n        access: Vec<Access>,\n        op: AssignOp,\n        rhe: Expression,\n    },\n    MultiSubstitution {\n        meta: Meta,\n        lhe: Expression,\n        op: AssignOp,\n        rhe: Expression,\n    },\n    ConstraintEquality {\n        meta: Meta,\n        lhe: Expression,\n        rhe: Expression,\n    },\n    LogCall {\n        meta: Meta,\n        args: Vec<LogArgument>,\n    },\n    Block {\n        meta: Meta,\n        stmts: Vec<Statement>,\n    },\n    Assert {\n        meta: Meta,\n        arg: Expression,\n    },\n}\n\n#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]\npub enum SignalElementType {\n    Empty,\n    Binary,\n    FieldElement,\n}\n\n#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]\npub enum SignalType {\n    Output,\n    Input,\n    Intermediate,\n}\n\n#[derive(Clone, PartialEq, Ord, PartialOrd, Eq)]\npub enum VariableType {\n    Var,\n    Signal(SignalType, TagList),\n    Component,\n    AnonymousComponent,\n}\n\n#[derive(Clone)]\npub enum Expression {\n    InfixOp {\n        meta: Meta,\n        lhe: Box<Expression>,\n        infix_op: ExpressionInfixOpcode,\n        rhe: Box<Expression>,\n    },\n    PrefixOp {\n        meta: Meta,\n        prefix_op: ExpressionPrefixOpcode,\n        rhe: Box<Expression>,\n    },\n    InlineSwitchOp {\n        meta: Meta,\n        cond: Box<Expression>,\n        if_true: Box<Expression>,\n        if_false: Box<Expression>,\n    },\n    ParallelOp {\n        meta: Meta,\n        rhe: Box<Expression>,\n    },\n    Variable {\n        meta: Meta,\n        name: String,\n        access: Vec<Access>,\n    },\n    Number(Meta, BigInt),\n    Call {\n        meta: Meta,\n        id: String,\n        args: Vec<Expression>,\n    },\n    AnonymousComponent {\n        meta: Meta,\n        id: String,\n        is_parallel: bool,\n        params: Vec<Expression>,\n        signals: Vec<Expression>,\n        names: Option<Vec<(AssignOp, String)>>,\n    },\n    // UniformArray is only used internally by Circom for default initialization\n    // of uninitialized arrays.\n    // UniformArray {\n    //     meta: Meta,\n    //     value: Box<Expression>,\n    //     dimension: Box<Expression>,\n    // },\n    ArrayInLine {\n        meta: Meta,\n        values: Vec<Expression>,\n    },\n    Tuple {\n        meta: Meta,\n        values: Vec<Expression>,\n    },\n}\n\n#[derive(Clone)]\npub enum Access {\n    ComponentAccess(String),\n    ArrayAccess(Expression),\n}\npub fn build_component_access(acc: String) -> Access {\n    Access::ComponentAccess(acc)\n}\npub fn build_array_access(expr: Expression) -> Access {\n    Access::ArrayAccess(expr)\n}\n\n#[derive(Copy, Clone, Eq, PartialEq)]\npub enum AssignOp {\n    AssignVar,\n    AssignSignal,\n    AssignConstraintSignal,\n}\n\n#[derive(Copy, Clone, PartialEq, Eq)]\npub enum ExpressionInfixOpcode {\n    Mul,\n    Div,\n    Add,\n    Sub,\n    Pow,\n    IntDiv,\n    Mod,\n    ShiftL,\n    ShiftR,\n    LesserEq,\n    GreaterEq,\n    Lesser,\n    Greater,\n    Eq,\n    NotEq,\n    BoolOr,\n    BoolAnd,\n    BitOr,\n    BitAnd,\n    BitXor,\n}\n\n#[derive(Copy, Clone, PartialEq, Eq)]\npub enum ExpressionPrefixOpcode {\n    Sub,\n    BoolNot,\n    Complement,\n}\n\n#[derive(Clone)]\npub enum LogArgument {\n    LogStr(String),\n    LogExp(Expression),\n}\n\npub fn build_log_string(acc: String) -> LogArgument {\n    LogArgument::LogStr(acc)\n}\n\npub fn build_log_expression(expr: Expression) -> LogArgument {\n    LogArgument::LogExp(expr)\n}\n\n// Knowledge buckets\n#[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq)]\npub enum TypeReduction {\n    Variable,\n    Component,\n    Signal,\n    Tag,\n}\n\n#[derive(Default, Clone)]\npub struct TypeKnowledge {\n    reduces_to: Option<TypeReduction>,\n}\nimpl TypeKnowledge {\n    pub fn new() -> TypeKnowledge {\n        TypeKnowledge::default()\n    }\n    pub fn set_reduces_to(&mut self, reduces_to: TypeReduction) {\n        self.reduces_to = Option::Some(reduces_to);\n    }\n    pub fn reduces_to(&self) -> TypeReduction {\n        if let Option::Some(t) = &self.reduces_to {\n            *t\n        } else {\n            panic!(\"Type knowledge accessed before it is initialized.\");\n        }\n    }\n    pub fn is_var(&self) -> bool {\n        self.reduces_to() == TypeReduction::Variable\n    }\n    pub fn is_component(&self) -> bool {\n        self.reduces_to() == TypeReduction::Component\n    }\n    pub fn is_signal(&self) -> bool {\n        self.reduces_to() == TypeReduction::Signal\n    }\n    pub fn is_tag(&self) -> bool {\n        self.reduces_to() == TypeReduction::Tag\n    }\n}\n\n#[derive(Default, Clone)]\npub struct MemoryKnowledge {\n    concrete_dimensions: Option<Vec<usize>>,\n    full_length: Option<usize>,\n    abstract_memory_address: Option<usize>,\n}\nimpl MemoryKnowledge {\n    pub fn new() -> MemoryKnowledge {\n        MemoryKnowledge::default()\n    }\n    pub fn set_concrete_dimensions(&mut self, value: Vec<usize>) {\n        self.full_length = Option::Some(value.iter().fold(1, |p, v| p * (*v)));\n        self.concrete_dimensions = Option::Some(value);\n    }\n    pub fn set_abstract_memory_address(&mut self, value: usize) {\n        self.abstract_memory_address = Option::Some(value);\n    }\n    pub fn concrete_dimensions(&self) -> &[usize] {\n        if let Option::Some(v) = &self.concrete_dimensions {\n            v\n        } else {\n            panic!(\"Concrete dimensions accessed before it is initialized.\");\n        }\n    }\n    pub fn full_length(&self) -> usize {\n        if let Option::Some(v) = &self.full_length {\n            *v\n        } else {\n            panic!(\"Full dimension accessed before it is initialized.\");\n        }\n    }\n    pub fn abstract_memory_address(&self) -> usize {\n        if let Option::Some(v) = &self.abstract_memory_address {\n            *v\n        } else {\n            panic!(\"Abstract memory address accessed before it is initialized.\");\n        }\n    }\n}\n"
  },
  {
    "path": "program_structure/src/abstract_syntax_tree/ast_impl.rs",
    "content": "use super::ast::*;\n\nimpl AST {\n    pub fn get_includes(&self) -> &Vec<Include> {\n        &self.includes\n    }\n\n    pub fn get_version(&self) -> &Option<Version> {\n        &self.compiler_version\n    }\n\n    pub fn get_definitions(&self) -> &Vec<Definition> {\n        &self.definitions\n    }\n    pub fn decompose(\n        self,\n    ) -> (Meta, Option<Version>, Vec<Include>, Vec<Definition>, Option<MainComponent>) {\n        (self.meta, self.compiler_version, self.includes, self.definitions, self.main_component)\n    }\n}\n"
  },
  {
    "path": "program_structure/src/abstract_syntax_tree/ast_shortcuts.rs",
    "content": "use super::ast::*;\nuse super::expression_builders::*;\nuse super::statement_builders::*;\nuse crate::ast::{Access, Expression, VariableType};\nuse num_bigint::BigInt;\n\n#[derive(Clone)]\npub struct Symbol {\n    pub name: String,\n    pub is_array: Vec<Expression>,\n    pub init: Option<Expression>,\n}\n\npub struct TupleInit {\n    pub tuple_init: (AssignOp, Expression),\n}\n\npub fn assign_with_op_shortcut(\n    op: ExpressionInfixOpcode,\n    meta: Meta,\n    variable: (String, Vec<Access>),\n    rhe: Expression,\n) -> Statement {\n    let (var, access) = variable;\n    let variable = build_variable(meta.clone(), var.clone(), access.clone());\n    let infix = build_infix(meta.clone(), variable, op, rhe);\n    build_substitution(meta, var, access, AssignOp::AssignVar, infix)\n}\n\npub fn plusplus(meta: Meta, variable: (String, Vec<Access>)) -> Statement {\n    let one = build_number(meta.clone(), BigInt::from(1));\n    assign_with_op_shortcut(ExpressionInfixOpcode::Add, meta, variable, one)\n}\n\npub fn subsub(meta: Meta, variable: (String, Vec<Access>)) -> Statement {\n    let one = build_number(meta.clone(), BigInt::from(1));\n    assign_with_op_shortcut(ExpressionInfixOpcode::Sub, meta, variable, one)\n}\n\npub fn for_into_while(\n    meta: Meta,\n    init: Statement,\n    cond: Expression,\n    step: Statement,\n    body: Statement,\n) -> Statement {\n    let while_body = build_block(body.get_meta().clone(), vec![body, step]);\n    let while_statement = build_while_block(meta.clone(), cond, while_body);\n    build_block(meta, vec![init, while_statement])\n}\n\npub fn split_declaration_into_single_nodes(\n    meta: Meta,\n    xtype: VariableType,\n    symbols: Vec<Symbol>,\n    op: AssignOp,\n) -> Statement {\n    // use crate::ast_shortcuts::VariableType::Var;\n    let mut initializations = Vec::new();\n\n    for symbol in symbols {\n        let with_meta = meta.clone();\n        let has_type = xtype.clone();\n        let name = symbol.name.clone();\n        let dimensions = symbol.is_array;\n        let possible_init = symbol.init;\n        let single_declaration = build_declaration(with_meta, has_type, name, dimensions.clone());\n        initializations.push(single_declaration);\n\n        if let Option::Some(init) = possible_init {\n            let substitution = build_substitution(meta.clone(), symbol.name, vec![], op, init);\n            initializations.push(substitution);\n        }\n        // If the variable is not initialized it is default initialized to 0 by\n        // Circom.  We remove this because we don't want this assignment to be\n        // flagged as an unused assignment by the side-effect analysis.\n        // else if xtype == Var {\n        //     let mut value = Expression::Number(meta.clone(), BigInt::from(0));\n        //     for dim_expr in dimensions.iter().rev() {\n        //         value = build_uniform_array(meta.clone(), value, dim_expr.clone());\n        //     }\n\n        //     let substitution = build_substitution(meta.clone(), symbol.name, vec![], op, value);\n        //     initializations.push(substitution);\n        // }\n    }\n    build_initialization_block(meta, xtype, initializations)\n}\n\npub fn split_declaration_into_single_nodes_and_multi_substitution(\n    meta: Meta,\n    xtype: VariableType,\n    symbols: Vec<Symbol>,\n    init: Option<TupleInit>,\n) -> Statement {\n    let mut initializations = Vec::new();\n    let mut values = Vec::new();\n    for symbol in symbols {\n        let with_meta = meta.clone();\n        let has_type = xtype.clone();\n        let name = symbol.name.clone();\n        let dimensions = symbol.is_array;\n        debug_assert!(symbol.init.is_none());\n        let single_declaration =\n            build_declaration(with_meta.clone(), has_type, name.clone(), dimensions.clone());\n        initializations.push(single_declaration);\n        // Circom default initializes local arrays to 0. We remove this because\n        // we don't want these assignments to be flagged as unused assignments\n        // by the side-effect analysis.\n        // if xtype == Var && init.is_none() {\n        //     let mut value = Expression::Number(meta.clone(), BigInt::from(0));\n        //     for dim_expr in dimensions.iter().rev() {\n        //         value = build_uniform_array(meta.clone(), value, dim_expr.clone());\n        //     }\n\n        //     let substitution =\n        //         build_substitution(meta.clone(), symbol.name, vec![], AssignOp::AssignVar, value);\n        //     initializations.push(substitution);\n        // }\n        values.push(Expression::Variable { meta: with_meta.clone(), name, access: Vec::new() })\n    }\n    if let Some(tuple) = init {\n        let (op, expression) = tuple.tuple_init;\n        let multi_sub = build_multi_substitution(\n            meta.clone(),\n            build_tuple(meta.clone(), values),\n            op,\n            expression,\n        );\n        initializations.push(multi_sub);\n    }\n    build_initialization_block(meta, xtype, initializations)\n}\n"
  },
  {
    "path": "program_structure/src/abstract_syntax_tree/expression_builders.rs",
    "content": "use super::ast::*;\nuse num_bigint::BigInt;\nuse Expression::*;\n\npub fn build_infix(\n    meta: Meta,\n    lhe: Expression,\n    infix_op: ExpressionInfixOpcode,\n    rhe: Expression,\n) -> Expression {\n    InfixOp { meta, infix_op, lhe: Box::new(lhe), rhe: Box::new(rhe) }\n}\n\npub fn build_prefix(meta: Meta, prefix_op: ExpressionPrefixOpcode, rhe: Expression) -> Expression {\n    PrefixOp { meta, prefix_op, rhe: Box::new(rhe) }\n}\n\npub fn build_inline_switch_op(\n    meta: Meta,\n    cond: Expression,\n    if_true: Expression,\n    if_false: Expression,\n) -> Expression {\n    InlineSwitchOp {\n        meta,\n        cond: Box::new(cond),\n        if_true: Box::new(if_true),\n        if_false: Box::new(if_false),\n    }\n}\n\npub fn build_parallel_op(meta: Meta, rhe: Expression) -> Expression {\n    ParallelOp { meta, rhe: Box::new(rhe) }\n}\n\npub fn build_variable(meta: Meta, name: String, access: Vec<Access>) -> Expression {\n    Variable { meta, name, access }\n}\n\npub fn build_number(meta: Meta, value: BigInt) -> Expression {\n    Number(meta, value)\n}\n\npub fn build_call(meta: Meta, id: String, args: Vec<Expression>) -> Expression {\n    Call { meta, id, args }\n}\n\npub fn build_anonymous_component(\n    meta: Meta,\n    id: String,\n    params: Vec<Expression>,\n    signals: Vec<Expression>,\n    names: Option<Vec<(AssignOp, String)>>,\n    is_parallel: bool,\n) -> Expression {\n    AnonymousComponent { meta, id, params, signals, names, is_parallel }\n}\n\npub fn build_array_in_line(meta: Meta, values: Vec<Expression>) -> Expression {\n    ArrayInLine { meta, values }\n}\n\npub fn build_tuple(meta: Meta, values: Vec<Expression>) -> Expression {\n    Tuple { meta, values }\n}\n\npub fn unzip_3(\n    vec: Vec<(String, AssignOp, Expression)>,\n) -> (Vec<(AssignOp, String)>, Vec<Expression>) {\n    let mut op_name = Vec::new();\n    let mut exprs = Vec::new();\n    for i in vec {\n        op_name.push((i.1, i.0));\n        exprs.push(i.2);\n    }\n    (op_name, exprs)\n}\n"
  },
  {
    "path": "program_structure/src/abstract_syntax_tree/expression_impl.rs",
    "content": "use std::fmt::{Debug, Display, Error, Formatter};\n\nuse super::ast::*;\nuse super::expression_builders::build_anonymous_component;\n\nimpl Expression {\n    pub fn meta(&self) -> &Meta {\n        use Expression::*;\n        match self {\n            InfixOp { meta, .. }\n            | PrefixOp { meta, .. }\n            | InlineSwitchOp { meta, .. }\n            | Variable { meta, .. }\n            | ParallelOp { meta, .. }\n            | Number(meta, ..)\n            | Call { meta, .. }\n            | AnonymousComponent { meta, .. }\n            | ArrayInLine { meta, .. }\n            | Tuple { meta, .. } => meta,\n        }\n    }\n    pub fn meta_mut(&mut self) -> &mut Meta {\n        use Expression::*;\n        match self {\n            InfixOp { meta, .. }\n            | PrefixOp { meta, .. }\n            | InlineSwitchOp { meta, .. }\n            | Variable { meta, .. }\n            | ParallelOp { meta, .. }\n            | Number(meta, ..)\n            | Call { meta, .. }\n            | AnonymousComponent { meta, .. }\n            | ArrayInLine { meta, .. }\n            | Tuple { meta, .. } => meta,\n        }\n    }\n\n    pub fn is_array(&self) -> bool {\n        use Expression::*;\n        matches!(self, ArrayInLine { .. })\n    }\n\n    pub fn is_infix(&self) -> bool {\n        use Expression::*;\n        matches!(self, InfixOp { .. })\n    }\n\n    pub fn is_prefix(&self) -> bool {\n        use Expression::*;\n        matches!(self, PrefixOp { .. })\n    }\n\n    pub fn is_switch(&self) -> bool {\n        use Expression::*;\n        matches!(self, InlineSwitchOp { .. })\n    }\n\n    pub fn is_variable(&self) -> bool {\n        use Expression::*;\n        matches!(self, Variable { .. })\n    }\n\n    pub fn is_number(&self) -> bool {\n        use Expression::*;\n        matches!(self, Number(..))\n    }\n\n    pub fn is_call(&self) -> bool {\n        use Expression::*;\n        matches!(self, Call { .. })\n    }\n\n    pub fn is_parallel(&self) -> bool {\n        use Expression::*;\n        matches!(self, ParallelOp { .. })\n    }\n\n    pub fn is_tuple(&self) -> bool {\n        use Expression::*;\n        matches!(self, Tuple { .. })\n    }\n\n    pub fn is_anonymous_component(&self) -> bool {\n        use Expression::*;\n        matches!(self, AnonymousComponent { .. })\n    }\n\n    pub fn make_anonymous_parallel(self) -> Expression {\n        use Expression::*;\n        match self {\n            AnonymousComponent { meta, id, params, signals, names, .. } => {\n                build_anonymous_component(meta, id, params, signals, names, true)\n            }\n            _ => self,\n        }\n    }\n}\n\nimpl FillMeta for Expression {\n    fn fill(&mut self, file_id: usize, element_id: &mut usize) {\n        use Expression::*;\n        self.meta_mut().elem_id = *element_id;\n        *element_id += 1;\n        match self {\n            Tuple { meta, values } => fill_tuple(meta, values, file_id, element_id),\n            Number(meta, _) => fill_number(meta, file_id, element_id),\n            Variable { meta, access, .. } => fill_variable(meta, access, file_id, element_id),\n            InfixOp { meta, lhe, rhe, .. } => fill_infix(meta, lhe, rhe, file_id, element_id),\n            PrefixOp { meta, rhe, .. } => fill_prefix(meta, rhe, file_id, element_id),\n            ParallelOp { meta, rhe, .. } => fill_parallel(meta, rhe, file_id, element_id),\n            InlineSwitchOp { meta, cond, if_false, if_true, .. } => {\n                fill_inline_switch_op(meta, cond, if_true, if_false, file_id, element_id)\n            }\n            Call { meta, args, .. } => fill_call(meta, args, file_id, element_id),\n            ArrayInLine { meta, values, .. } => {\n                fill_array_inline(meta, values, file_id, element_id)\n            }\n            AnonymousComponent { meta, params, signals, .. } => {\n                fill_anonymous_component(meta, params, signals, file_id, element_id)\n            }\n        }\n    }\n}\n\nfn fill_number(meta: &mut Meta, file_id: usize, _element_id: &mut usize) {\n    meta.set_file_id(file_id);\n}\n\nfn fill_variable(meta: &mut Meta, access: &mut [Access], file_id: usize, element_id: &mut usize) {\n    meta.set_file_id(file_id);\n    for acc in access {\n        if let Access::ArrayAccess(e) = acc {\n            e.fill(file_id, element_id)\n        }\n    }\n}\n\nfn fill_infix(\n    meta: &mut Meta,\n    lhe: &mut Expression,\n    rhe: &mut Expression,\n    file_id: usize,\n    element_id: &mut usize,\n) {\n    meta.set_file_id(file_id);\n    lhe.fill(file_id, element_id);\n    rhe.fill(file_id, element_id);\n}\n\nfn fill_prefix(meta: &mut Meta, rhe: &mut Expression, file_id: usize, element_id: &mut usize) {\n    meta.set_file_id(file_id);\n    rhe.fill(file_id, element_id);\n}\n\nfn fill_inline_switch_op(\n    meta: &mut Meta,\n    cond: &mut Expression,\n    if_true: &mut Expression,\n    if_false: &mut Expression,\n    file_id: usize,\n    element_id: &mut usize,\n) {\n    meta.set_file_id(file_id);\n    cond.fill(file_id, element_id);\n    if_true.fill(file_id, element_id);\n    if_false.fill(file_id, element_id);\n}\n\nfn fill_call(meta: &mut Meta, args: &mut [Expression], file_id: usize, element_id: &mut usize) {\n    meta.set_file_id(file_id);\n    for a in args {\n        a.fill(file_id, element_id);\n    }\n}\n\nfn fill_array_inline(\n    meta: &mut Meta,\n    values: &mut [Expression],\n    file_id: usize,\n    element_id: &mut usize,\n) {\n    meta.set_file_id(file_id);\n    for v in values {\n        v.fill(file_id, element_id);\n    }\n}\n\nfn fill_anonymous_component(\n    meta: &mut Meta,\n    params: &mut [Expression],\n    signals: &mut [Expression],\n    file_id: usize,\n    element_id: &mut usize,\n) {\n    meta.set_file_id(file_id);\n    for param in params {\n        param.fill(file_id, element_id);\n    }\n    for signal in signals {\n        signal.fill(file_id, element_id);\n    }\n}\n\nfn fill_tuple(meta: &mut Meta, values: &mut [Expression], file_id: usize, element_id: &mut usize) {\n    meta.set_file_id(file_id);\n    for value in values {\n        value.fill(file_id, element_id);\n    }\n}\n\nfn fill_parallel(meta: &mut Meta, rhe: &mut Expression, file_id: usize, element_id: &mut usize) {\n    meta.set_file_id(file_id);\n    rhe.fill(file_id, element_id);\n}\n\nimpl Debug for Expression {\n    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {\n        use Expression::*;\n        match self {\n            InfixOp { .. } => write!(f, \"Expression::InfixOp\"),\n            PrefixOp { .. } => write!(f, \"Expression::PrefixOp\"),\n            InlineSwitchOp { .. } => write!(f, \"Expression::InlineSwitchOp\"),\n            Variable { .. } => write!(f, \"Expression::Variable\"),\n            ParallelOp { .. } => write!(f, \"Expression::ParallelOp\"),\n            Number(..) => write!(f, \"Expression::Number\"),\n            Call { .. } => write!(f, \"Expression::Call\"),\n            AnonymousComponent { .. } => write!(f, \"Expression::AnonymousComponent\"),\n            ArrayInLine { .. } => write!(f, \"Expression::ArrayInline\"),\n            Tuple { .. } => write!(f, \"Expression::Tuple\"),\n        }\n    }\n}\n\nimpl Display for Expression {\n    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {\n        use Expression::*;\n        match self {\n            Tuple { values, .. } => write!(f, \"({})\", vec_to_string(values)),\n            Number(_, value) => write!(f, \"{value}\"),\n            Variable { name, access, .. } => {\n                write!(f, \"{name}\")?;\n                for access in access {\n                    write!(f, \"{access}\")?;\n                }\n                Ok(())\n            }\n            ParallelOp { rhe, .. } => write!(f, \"parallel {rhe}\"),\n            InfixOp { lhe, infix_op, rhe, .. } => write!(f, \"({lhe} {infix_op} {rhe})\"),\n            PrefixOp { prefix_op, rhe, .. } => write!(f, \"{prefix_op}({rhe})\"),\n            InlineSwitchOp { cond, if_true, if_false, .. } => {\n                write!(f, \"({cond}? {if_true} : {if_false})\")\n            }\n            Call { id, args, .. } => write!(f, \"{id}({})\", vec_to_string(args)),\n            ArrayInLine { values, .. } => write!(f, \"[{}]\", vec_to_string(values)),\n            AnonymousComponent { id, params, signals, names, .. } => {\n                write!(f, \"{id}({})({})\", vec_to_string(params), signals_to_string(names, signals))\n            }\n        }\n    }\n}\n\nimpl Display for ExpressionInfixOpcode {\n    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {\n        use ExpressionInfixOpcode::*;\n        match self {\n            Mul => f.write_str(\"*\"),\n            Div => f.write_str(\"/\"),\n            Add => f.write_str(\"+\"),\n            Sub => f.write_str(\"-\"),\n            Pow => f.write_str(\"**\"),\n            IntDiv => f.write_str(\"\\\\\"),\n            Mod => f.write_str(\"%\"),\n            ShiftL => f.write_str(\"<<\"),\n            ShiftR => f.write_str(\">>\"),\n            LesserEq => f.write_str(\"<=\"),\n            GreaterEq => f.write_str(\">=\"),\n            Lesser => f.write_str(\"<\"),\n            Greater => f.write_str(\">\"),\n            Eq => f.write_str(\"==\"),\n            NotEq => f.write_str(\"!=\"),\n            BoolOr => f.write_str(\"||\"),\n            BoolAnd => f.write_str(\"&&\"),\n            BitOr => f.write_str(\"|\"),\n            BitAnd => f.write_str(\"&\"),\n            BitXor => f.write_str(\"^\"),\n        }\n    }\n}\n\nimpl Display for ExpressionPrefixOpcode {\n    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {\n        use ExpressionPrefixOpcode::*;\n        match self {\n            Sub => f.write_str(\"-\"),\n            BoolNot => f.write_str(\"!\"),\n            Complement => f.write_str(\"~\"),\n        }\n    }\n}\n\nimpl Display for Access {\n    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {\n        use Access::*;\n        match self {\n            ArrayAccess(index) => write!(f, \"[{index}]\"),\n            ComponentAccess(name) => write!(f, \".{name}\"),\n        }\n    }\n}\n\nfn vec_to_string(elems: &[Expression]) -> String {\n    elems.iter().map(|arg| arg.to_string()).collect::<Vec<String>>().join(\", \")\n}\n\nfn signals_to_string(names: &Option<Vec<(AssignOp, String)>>, signals: &[Expression]) -> String {\n    if let Some(names) = names {\n        names\n            .iter()\n            .zip(signals.iter())\n            .map(|((op, name), signal)| format!(\"{name} {op} {signal}\"))\n            .collect::<Vec<_>>()\n    } else {\n        signals.iter().map(|signal| signal.to_string()).collect::<Vec<_>>()\n    }\n    .join(\", \")\n}\n"
  },
  {
    "path": "program_structure/src/abstract_syntax_tree/mod.rs",
    "content": "mod assign_op_impl;\npub mod ast;\nmod ast_impl;\npub mod ast_shortcuts;\npub mod expression_builders;\nmod expression_impl;\npub mod statement_builders;\nmod statement_impl;\n"
  },
  {
    "path": "program_structure/src/abstract_syntax_tree/statement_builders.rs",
    "content": "use super::ast::*;\nuse Statement::*;\n\npub fn build_conditional_block(\n    meta: Meta,\n    cond: Expression,\n    if_case: Statement,\n    else_case: Option<Statement>,\n) -> Statement {\n    IfThenElse { meta, cond, else_case: else_case.map(Box::new), if_case: Box::new(if_case) }\n}\n\npub fn build_while_block(meta: Meta, cond: Expression, stmt: Statement) -> Statement {\n    While { meta, cond, stmt: Box::new(stmt) }\n}\n\npub fn build_initialization_block(\n    meta: Meta,\n    xtype: VariableType,\n    initializations: Vec<Statement>,\n) -> Statement {\n    InitializationBlock { meta, xtype, initializations }\n}\npub fn build_block(meta: Meta, stmts: Vec<Statement>) -> Statement {\n    Block { meta, stmts }\n}\n\npub fn build_return(meta: Meta, value: Expression) -> Statement {\n    Return { meta, value }\n}\n\npub fn build_declaration(\n    meta: Meta,\n    xtype: VariableType,\n    name: String,\n    dimensions: Vec<Expression>,\n) -> Statement {\n    let is_constant = true;\n    Declaration { meta, xtype, name, dimensions, is_constant }\n}\n\npub fn build_substitution(\n    meta: Meta,\n    var: String,\n    access: Vec<Access>,\n    op: AssignOp,\n    rhe: Expression,\n) -> Statement {\n    Substitution { meta, var, access, op, rhe }\n}\n\npub fn build_constraint_equality(meta: Meta, lhe: Expression, rhe: Expression) -> Statement {\n    ConstraintEquality { meta, lhe, rhe }\n}\n\npub fn build_log_call(meta: Meta, args: Vec<LogArgument>) -> Statement {\n    let mut new_args = Vec::new();\n    for arg in args {\n        match arg {\n            LogArgument::LogExp(..) => {\n                new_args.push(arg);\n            }\n            LogArgument::LogStr(str) => {\n                new_args.append(&mut split_string(str));\n            }\n        }\n    }\n    LogCall { meta, args: new_args }\n}\n\nfn split_string(str: String) -> Vec<LogArgument> {\n    let mut v = vec![];\n    let sub_len = 230;\n    let mut cur = str;\n    while !cur.is_empty() {\n        let (chunk, rest) = cur.split_at(std::cmp::min(sub_len, cur.len()));\n        v.push(LogArgument::LogStr(chunk.to_string()));\n        cur = rest.to_string();\n    }\n    v\n}\n\npub fn build_assert(meta: Meta, arg: Expression) -> Statement {\n    Assert { meta, arg }\n}\n\npub fn build_multi_substitution(\n    meta: Meta,\n    lhe: Expression,\n    op: AssignOp,\n    rhe: Expression,\n) -> Statement {\n    MultiSubstitution { meta, lhe, op, rhe }\n}\n\npub fn build_anonymous_component_statement(meta: Meta, arg: Expression) -> Statement {\n    MultiSubstitution {\n        meta: meta.clone(),\n        lhe: crate::expression_builders::build_tuple(meta, Vec::new()),\n        op: AssignOp::AssignConstraintSignal,\n        rhe: arg,\n    }\n}\n"
  },
  {
    "path": "program_structure/src/abstract_syntax_tree/statement_impl.rs",
    "content": "use std::fmt::{Debug, Display, Error, Formatter};\n\nuse super::ast::*;\n\nimpl Statement {\n    pub fn get_meta(&self) -> &Meta {\n        use Statement::*;\n        match self {\n            IfThenElse { meta, .. }\n            | While { meta, .. }\n            | Return { meta, .. }\n            | Declaration { meta, .. }\n            | Substitution { meta, .. }\n            | MultiSubstitution { meta, .. }\n            | LogCall { meta, .. }\n            | Block { meta, .. }\n            | Assert { meta, .. }\n            | ConstraintEquality { meta, .. }\n            | InitializationBlock { meta, .. } => meta,\n        }\n    }\n    pub fn get_mut_meta(&mut self) -> &mut Meta {\n        use Statement::*;\n        match self {\n            IfThenElse { meta, .. }\n            | While { meta, .. }\n            | Return { meta, .. }\n            | Declaration { meta, .. }\n            | Substitution { meta, .. }\n            | MultiSubstitution { meta, .. }\n            | LogCall { meta, .. }\n            | Block { meta, .. }\n            | Assert { meta, .. }\n            | ConstraintEquality { meta, .. }\n            | InitializationBlock { meta, .. } => meta,\n        }\n    }\n\n    pub fn is_if_then_else(&self) -> bool {\n        use Statement::*;\n        matches!(self, IfThenElse { .. })\n    }\n\n    pub fn is_while(&self) -> bool {\n        use Statement::*;\n        matches!(self, While { .. })\n    }\n\n    pub fn is_return(&self) -> bool {\n        use Statement::*;\n        matches!(self, Return { .. })\n    }\n\n    pub fn is_initialization_block(&self) -> bool {\n        use Statement::*;\n        matches!(self, InitializationBlock { .. })\n    }\n\n    pub fn is_declaration(&self) -> bool {\n        use Statement::*;\n        matches!(self, Declaration { .. })\n    }\n\n    pub fn is_substitution(&self) -> bool {\n        use Statement::*;\n        matches!(self, Substitution { .. })\n    }\n\n    pub fn is_multi_substitution(&self) -> bool {\n        use Statement::*;\n        matches!(self, MultiSubstitution { .. })\n    }\n\n    pub fn is_constraint_equality(&self) -> bool {\n        use Statement::*;\n        matches!(self, ConstraintEquality { .. })\n    }\n\n    pub fn is_log_call(&self) -> bool {\n        use Statement::*;\n        matches!(self, LogCall { .. })\n    }\n\n    pub fn is_block(&self) -> bool {\n        use Statement::*;\n        matches!(self, Block { .. })\n    }\n\n    pub fn is_assert(&self) -> bool {\n        use Statement::*;\n        matches!(self, Assert { .. })\n    }\n}\n\nimpl FillMeta for Statement {\n    fn fill(&mut self, file_id: usize, element_id: &mut usize) {\n        use Statement::*;\n        self.get_mut_meta().elem_id = *element_id;\n        *element_id += 1;\n        match self {\n            IfThenElse { meta, cond, if_case, else_case, .. } => {\n                fill_conditional(meta, cond, if_case, else_case, file_id, element_id)\n            }\n            While { meta, cond, stmt } => fill_while(meta, cond, stmt, file_id, element_id),\n            Return { meta, value } => fill_return(meta, value, file_id, element_id),\n            InitializationBlock { meta, initializations, .. } => {\n                fill_initialization(meta, initializations, file_id, element_id)\n            }\n            Declaration { meta, dimensions, .. } => {\n                fill_declaration(meta, dimensions, file_id, element_id)\n            }\n            Substitution { meta, access, rhe, .. } => {\n                fill_substitution(meta, access, rhe, file_id, element_id)\n            }\n            MultiSubstitution { meta, lhe, rhe, .. } => {\n                fill_multi_substitution(meta, lhe, rhe, file_id, element_id);\n            }\n            ConstraintEquality { meta, lhe, rhe } => {\n                fill_constraint_equality(meta, lhe, rhe, file_id, element_id)\n            }\n            LogCall { meta, args, .. } => fill_log_call(meta, args, file_id, element_id),\n            Block { meta, stmts, .. } => fill_block(meta, stmts, file_id, element_id),\n            Assert { meta, arg, .. } => fill_assert(meta, arg, file_id, element_id),\n        }\n    }\n}\n\nfn fill_conditional(\n    meta: &mut Meta,\n    cond: &mut Expression,\n    if_case: &mut Statement,\n    else_case: &mut Option<Box<Statement>>,\n    file_id: usize,\n    element_id: &mut usize,\n) {\n    meta.set_file_id(file_id);\n    cond.fill(file_id, element_id);\n    if_case.fill(file_id, element_id);\n    if let Option::Some(s) = else_case {\n        s.fill(file_id, element_id);\n    }\n}\n\nfn fill_while(\n    meta: &mut Meta,\n    cond: &mut Expression,\n    stmt: &mut Statement,\n    file_id: usize,\n    element_id: &mut usize,\n) {\n    meta.set_file_id(file_id);\n    cond.fill(file_id, element_id);\n    stmt.fill(file_id, element_id);\n}\n\nfn fill_return(meta: &mut Meta, value: &mut Expression, file_id: usize, element_id: &mut usize) {\n    meta.set_file_id(file_id);\n    value.fill(file_id, element_id);\n}\n\nfn fill_initialization(\n    meta: &mut Meta,\n    initializations: &mut [Statement],\n    file_id: usize,\n    element_id: &mut usize,\n) {\n    meta.set_file_id(file_id);\n    for init in initializations {\n        init.fill(file_id, element_id);\n    }\n}\n\nfn fill_declaration(\n    meta: &mut Meta,\n    dimensions: &mut [Expression],\n    file_id: usize,\n    element_id: &mut usize,\n) {\n    meta.set_file_id(file_id);\n    for d in dimensions {\n        d.fill(file_id, element_id);\n    }\n}\n\nfn fill_substitution(\n    meta: &mut Meta,\n    access: &mut [Access],\n    rhe: &mut Expression,\n    file_id: usize,\n    element_id: &mut usize,\n) {\n    meta.set_file_id(file_id);\n    rhe.fill(file_id, element_id);\n    for a in access {\n        if let Access::ArrayAccess(e) = a {\n            e.fill(file_id, element_id);\n        }\n    }\n}\n\nfn fill_multi_substitution(\n    meta: &mut Meta,\n    lhe: &mut Expression,\n    rhe: &mut Expression,\n    file_id: usize,\n    element_id: &mut usize,\n) {\n    meta.set_file_id(file_id);\n    rhe.fill(file_id, element_id);\n    lhe.fill(file_id, element_id);\n}\n\nfn fill_constraint_equality(\n    meta: &mut Meta,\n    lhe: &mut Expression,\n    rhe: &mut Expression,\n    file_id: usize,\n    element_id: &mut usize,\n) {\n    meta.set_file_id(file_id);\n    lhe.fill(file_id, element_id);\n    rhe.fill(file_id, element_id);\n}\n\nfn fill_log_call(\n    meta: &mut Meta,\n    args: &mut Vec<LogArgument>,\n    file_id: usize,\n    element_id: &mut usize,\n) {\n    meta.set_file_id(file_id);\n    for arg in args {\n        if let LogArgument::LogExp(e) = arg {\n            e.fill(file_id, element_id);\n        }\n    }\n}\n\nfn fill_block(meta: &mut Meta, stmts: &mut [Statement], file_id: usize, element_id: &mut usize) {\n    meta.set_file_id(file_id);\n    for s in stmts {\n        s.fill(file_id, element_id);\n    }\n}\n\nfn fill_assert(meta: &mut Meta, arg: &mut Expression, file_id: usize, element_id: &mut usize) {\n    meta.set_file_id(file_id);\n    arg.fill(file_id, element_id);\n}\n\nimpl Debug for Statement {\n    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {\n        use Statement::*;\n        match self {\n            IfThenElse { .. } => write!(f, \"Statement::IfThenElse\"),\n            While { .. } => write!(f, \"Statement::While\"),\n            Return { .. } => write!(f, \"Statement::Return\"),\n            Declaration { .. } => write!(f, \"Statement::Declaration\"),\n            Substitution { .. } => write!(f, \"Statement::Substitution\"),\n            MultiSubstitution { .. } => write!(f, \"Statement::MultiSubstitution\"),\n            LogCall { .. } => write!(f, \"Statement::LogCall\"),\n            Block { .. } => write!(f, \"Statement::Block\"),\n            Assert { .. } => write!(f, \"Statement::Assert\"),\n            ConstraintEquality { .. } => write!(f, \"Statement::ConstraintEquality\"),\n            InitializationBlock { .. } => write!(f, \"Statement::InitializationBlock\"),\n        }\n    }\n}\n\nimpl Display for Statement {\n    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {\n        use Statement::*;\n        match self {\n            IfThenElse { cond, else_case, .. } => match else_case {\n                Some(_) => write!(f, \"if {cond} else\"),\n                None => write!(f, \"if {cond}\"),\n            },\n            While { cond, .. } => write!(f, \"while {cond}\"),\n            Return { value, .. } => write!(f, \"return {value}\"),\n            Declaration { name, xtype, .. } => write!(f, \"{xtype} {name}\"),\n            Substitution { var, access, op, rhe, .. } => {\n                write!(f, \"{var}\")?;\n                for access in access {\n                    write!(f, \"{access}\")?;\n                }\n                write!(f, \" {op} {rhe}\")\n            }\n            MultiSubstitution { lhe, op, rhe, .. } => write!(f, \"{lhe} {op} {rhe}\"),\n            LogCall { args, .. } => write!(f, \"log({})\", vec_to_string(args)),\n            Block { .. } => Ok(()),\n            Assert { arg, .. } => write!(f, \"assert({arg})\"),\n            ConstraintEquality { lhe, rhe, .. } => write!(f, \"{lhe} === {rhe}\"),\n            InitializationBlock { .. } => Ok(()),\n        }\n    }\n}\n\nimpl Display for AssignOp {\n    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {\n        use AssignOp::*;\n        match self {\n            AssignVar => write!(f, \"=\"),\n            AssignSignal => write!(f, \"<--\"),\n            AssignConstraintSignal => write!(f, \"<==\"),\n        }\n    }\n}\n\nimpl Display for VariableType {\n    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {\n        use SignalType::*;\n        use VariableType::*;\n        match self {\n            Var => write!(f, \"var\"),\n            Signal(signal_type, tag_list) => {\n                if matches!(signal_type, Intermediate) {\n                    write!(f, \"signal\")?;\n                } else {\n                    write!(f, \"signal {signal_type}\")?;\n                }\n                if !tag_list.is_empty() {\n                    write!(f, \" {{{}}}\", tag_list.join(\"}} {{\"))\n                } else {\n                    Ok(())\n                }\n            }\n            Component => write!(f, \"component\"),\n            AnonymousComponent => write!(f, \"anonymous component\"),\n        }\n    }\n}\n\nimpl Display for SignalType {\n    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {\n        use SignalType::*;\n        match self {\n            Input => write!(f, \"input\"),\n            Output => write!(f, \"output\"),\n            Intermediate => write!(f, \"\"),\n        }\n    }\n}\n\nimpl Display for LogArgument {\n    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {\n        use LogArgument::*;\n        match self {\n            LogStr(message) => write!(f, \"{message}\"),\n            LogExp(value) => write!(f, \"{value}\"),\n        }\n    }\n}\n\nfn vec_to_string<T: ToString>(elems: &[T]) -> String {\n    elems.iter().map(|arg| arg.to_string()).collect::<Vec<String>>().join(\", \")\n}\n"
  },
  {
    "path": "program_structure/src/control_flow_graph/basic_block.rs",
    "content": "use log::trace;\nuse std::collections::HashSet;\nuse std::fmt;\n\nuse crate::ir::declarations::Declarations;\nuse crate::ir::degree_meta::DegreeEnvironment;\nuse crate::ir::value_meta::ValueEnvironment;\nuse crate::ssa::traits::DirectedGraphNode;\n\nuse crate::ir::variable_meta::{VariableMeta, VariableUses};\nuse crate::ir::{Meta, Statement};\n\ntype Index = usize;\ntype IndexSet = HashSet<Index>;\n\n#[derive(Clone)]\npub struct BasicBlock {\n    index: Index,\n    meta: Meta,\n    loop_depth: usize,\n    stmts: Vec<Statement>,\n    predecessors: IndexSet,\n    successors: IndexSet,\n}\n\nimpl BasicBlock {\n    #[must_use]\n    pub fn new(meta: Meta, index: Index, loop_depth: usize) -> BasicBlock {\n        trace!(\"creating basic block {index}\");\n        BasicBlock {\n            meta,\n            index,\n            loop_depth,\n            stmts: Vec::new(),\n            predecessors: IndexSet::new(),\n            successors: IndexSet::new(),\n        }\n    }\n\n    #[must_use]\n    pub fn from_raw_parts(\n        index: Index,\n        meta: Meta,\n        loop_depth: usize,\n        stmts: Vec<Statement>,\n        predecessors: IndexSet,\n        successors: IndexSet,\n    ) -> BasicBlock {\n        BasicBlock { index, meta, loop_depth, stmts, predecessors, successors }\n    }\n\n    #[must_use]\n    pub fn len(&self) -> usize {\n        self.stmts.len()\n    }\n\n    #[must_use]\n    pub fn is_empty(&self) -> bool {\n        self.len() == 0\n    }\n\n    #[must_use]\n    pub fn in_loop(&self) -> bool {\n        self.loop_depth > 0\n    }\n\n    #[must_use]\n    pub fn loop_depth(&self) -> usize {\n        self.loop_depth\n    }\n\n    pub fn iter(&self) -> impl Iterator<Item = &Statement> {\n        self.stmts.iter()\n    }\n\n    pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = &mut Statement> {\n        self.stmts.iter_mut()\n    }\n    #[must_use]\n    pub fn index(&self) -> Index {\n        self.index\n    }\n\n    #[must_use]\n    pub fn meta(&self) -> &Meta {\n        &self.meta\n    }\n\n    #[must_use]\n    pub(crate) fn meta_mut(&mut self) -> &mut Meta {\n        &mut self.meta\n    }\n\n    #[must_use]\n    pub fn statements(&self) -> &Vec<Statement> {\n        &self.stmts\n    }\n\n    #[must_use]\n    pub(crate) fn statements_mut(&mut self) -> &mut Vec<Statement> {\n        &mut self.stmts\n    }\n\n    pub(crate) fn prepend_statement(&mut self, stmt: Statement) {\n        self.stmts.insert(0, stmt);\n    }\n\n    pub(crate) fn append_statement(&mut self, stmt: Statement) {\n        self.stmts.push(stmt);\n    }\n\n    #[must_use]\n    pub fn predecessors(&self) -> &IndexSet {\n        &self.predecessors\n    }\n\n    #[must_use]\n    pub fn successors(&self) -> &IndexSet {\n        &self.successors\n    }\n\n    pub(crate) fn add_predecessor(&mut self, predecessor: Index) {\n        trace!(\"adding predecessor {} to basic block {}\", predecessor, self.index);\n        self.predecessors.insert(predecessor);\n    }\n\n    pub(crate) fn add_successor(&mut self, successor: Index) {\n        trace!(\"adding successor {} to basic block {}\", successor, self.index);\n        self.successors.insert(successor);\n    }\n\n    pub fn propagate_degrees(&mut self, env: &mut DegreeEnvironment) -> bool {\n        trace!(\"propagating degree ranges for basic block {}\", self.index());\n        let mut result = false;\n        for stmt in self.iter_mut() {\n            result = result || stmt.propagate_degrees(env);\n        }\n        result\n    }\n\n    pub fn propagate_values(&mut self, env: &mut ValueEnvironment) -> bool {\n        trace!(\"propagating values for basic block {}\", self.index());\n        let mut result = false;\n        for stmt in self.iter_mut() {\n            result = result || stmt.propagate_values(env);\n        }\n        result\n    }\n\n    pub fn propagate_types(&mut self, vars: &Declarations) {\n        trace!(\"propagating variable types for basic block {}\", self.index());\n        for stmt in self.iter_mut() {\n            stmt.propagate_types(vars);\n        }\n    }\n}\n\nimpl DirectedGraphNode for BasicBlock {\n    fn index(&self) -> Index {\n        self.index\n    }\n    fn predecessors(&self) -> &IndexSet {\n        &self.predecessors\n    }\n    fn successors(&self) -> &IndexSet {\n        &self.successors\n    }\n}\n\nimpl VariableMeta for BasicBlock {\n    fn cache_variable_use(&mut self) {\n        trace!(\"computing variable use for basic block {}\", self.index());\n        // Variable use for the block is simply the union of the variable use\n        // over all statements in the block.\n        for stmt in self.iter_mut() {\n            stmt.cache_variable_use();\n        }\n\n        // Cache variables read.\n        let locals_read = self.iter().flat_map(|stmt| stmt.locals_read()).cloned().collect();\n\n        // Cache variables written.\n        let locals_written = self.iter().flat_map(|stmt| stmt.locals_written()).cloned().collect();\n\n        // Cache signals read.\n        let signals_read = self.iter().flat_map(|stmt| stmt.signals_read()).cloned().collect();\n\n        // Cache signals written.\n        let signals_written =\n            self.iter().flat_map(|stmt| stmt.signals_written()).cloned().collect();\n\n        // Cache components read.\n        let components_read =\n            self.iter().flat_map(|stmt| stmt.components_read()).cloned().collect();\n\n        // Cache components written.\n        let components_written =\n            self.iter().flat_map(|stmt| stmt.components_written()).cloned().collect();\n\n        self.meta_mut()\n            .variable_knowledge_mut()\n            .set_locals_read(&locals_read)\n            .set_locals_written(&locals_written)\n            .set_signals_read(&signals_read)\n            .set_signals_written(&signals_written)\n            .set_components_read(&components_read)\n            .set_components_written(&components_written);\n    }\n\n    fn locals_read(&self) -> &VariableUses {\n        self.meta().variable_knowledge().locals_read()\n    }\n\n    fn locals_written(&self) -> &VariableUses {\n        self.meta().variable_knowledge().locals_written()\n    }\n\n    fn signals_read(&self) -> &VariableUses {\n        self.meta().variable_knowledge().signals_read()\n    }\n\n    fn signals_written(&self) -> &VariableUses {\n        self.meta().variable_knowledge().signals_written()\n    }\n\n    fn components_read(&self) -> &VariableUses {\n        self.meta().variable_knowledge().components_read()\n    }\n\n    fn components_written(&self) -> &VariableUses {\n        self.meta().variable_knowledge().components_written()\n    }\n}\n\nimpl fmt::Debug for BasicBlock {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        let lines = self.iter().map(|stmt| format!(\"{stmt:?}\")).collect::<Vec<_>>();\n        let width = lines.iter().map(|line| line.len()).max().unwrap_or_default();\n        let border = format!(\"+{}+\", (0..width + 2).map(|_| '-').collect::<String>());\n\n        writeln!(f, \"{}\", &border)?;\n        for line in lines {\n            writeln!(f, \"| {line:width$} |\")?;\n        }\n        writeln!(f, \"{}\", &border)\n    }\n}\n"
  },
  {
    "path": "program_structure/src/control_flow_graph/cfg.rs",
    "content": "use log::debug;\nuse std::collections::HashSet;\nuse std::fmt;\nuse std::time::{Instant, Duration};\n\nuse crate::constants::UsefulConstants;\nuse crate::file_definition::FileID;\nuse crate::ir::declarations::{Declaration, Declarations};\nuse crate::ir::degree_meta::{DegreeEnvironment, Degree, DegreeRange};\nuse crate::ir::value_meta::ValueEnvironment;\nuse crate::ir::variable_meta::VariableMeta;\nuse crate::ir::{VariableName, VariableType, SignalType};\nuse crate::ssa::dominator_tree::DominatorTree;\nuse crate::ssa::errors::SSAResult;\nuse crate::ssa::{insert_phi_statements, insert_ssa_variables};\n\nuse super::basic_block::BasicBlock;\nuse super::parameters::Parameters;\nuse super::ssa_impl;\nuse super::ssa_impl::{Config, Environment};\n\n/// Basic block index type.\npub type Index = usize;\n\nconst MAX_ANALYSIS_DURATION: Duration = Duration::from_secs(10);\n\n#[derive(Clone)]\npub enum DefinitionType {\n    Function,\n    Template,\n    CustomTemplate,\n}\n\nimpl fmt::Display for DefinitionType {\n    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {\n        match self {\n            DefinitionType::Function => write!(f, \"function\"),\n            DefinitionType::Template => write!(f, \"template\"),\n            DefinitionType::CustomTemplate => write!(f, \"custom template\"),\n        }\n    }\n}\n\npub struct Cfg {\n    name: String,\n    constants: UsefulConstants,\n    parameters: Parameters,\n    declarations: Declarations,\n    basic_blocks: Vec<BasicBlock>,\n    definition_type: DefinitionType,\n    dominator_tree: DominatorTree<BasicBlock>,\n}\n\nimpl Cfg {\n    pub(crate) fn new(\n        name: String,\n        constants: UsefulConstants,\n        definition_type: DefinitionType,\n        parameters: Parameters,\n        declarations: Declarations,\n        basic_blocks: Vec<BasicBlock>,\n        dominator_tree: DominatorTree<BasicBlock>,\n    ) -> Cfg {\n        Cfg {\n            name,\n            constants,\n            parameters,\n            declarations,\n            basic_blocks,\n            definition_type,\n            dominator_tree,\n        }\n    }\n    /// Returns the entry (first) block of the CFG.\n    #[must_use]\n    pub fn entry_block(&self) -> &BasicBlock {\n        &self.basic_blocks[Index::default()]\n    }\n\n    #[must_use]\n    pub fn get_basic_block(&self, index: Index) -> Option<&BasicBlock> {\n        self.basic_blocks.get(index)\n    }\n\n    /// Returns the number of basic blocks in the CFG.\n    #[must_use]\n    pub fn len(&self) -> usize {\n        self.basic_blocks.len()\n    }\n\n    /// Returns true if the CFG is empty.\n    #[must_use]\n    pub fn is_empty(&self) -> bool {\n        self.basic_blocks.is_empty()\n    }\n\n    /// Convert the CFG into SSA form.\n    pub fn into_ssa(mut self) -> SSAResult<Cfg> {\n        debug!(\"converting `{}` CFG to SSA\", self.name());\n\n        // 1. Insert phi statements and convert variables to SSA.\n        let mut env = Environment::new(self.parameters(), self.declarations());\n        insert_phi_statements::<Config>(&mut self.basic_blocks, &self.dominator_tree, &mut env);\n        insert_ssa_variables::<Config>(&mut self.basic_blocks, &self.dominator_tree, &mut env)?;\n\n        // 2. Update parameters to SSA form.\n        for name in self.parameters.iter_mut() {\n            *name = name.with_version(0);\n        }\n\n        // 3. Update declarations to track SSA variables.\n        self.declarations =\n            ssa_impl::update_declarations(&mut self.basic_blocks, &self.parameters, &env);\n\n        // 4. Propagate metadata to all child nodes. Since determining variable\n        // use requires that variable types are available, type propagation must\n        // run before caching variable use.\n        self.propagate_types();\n        self.propagate_values();\n        self.propagate_degrees();\n        self.cache_variable_use();\n\n        // 5. Print trace output of CFG.\n        for basic_block in self.basic_blocks.iter() {\n            debug!(\n                \"basic block {}: (predecessors: {:?}, successors: {:?})\",\n                basic_block.index(),\n                basic_block.predecessors(),\n                basic_block.successors(),\n            );\n            for stmt in basic_block.iter() {\n                debug!(\"    {stmt:?}\")\n            }\n        }\n        Ok(self)\n    }\n\n    /// Get the name of the corresponding function or template.\n    #[must_use]\n    pub fn name(&self) -> &str {\n        &self.name\n    }\n\n    /// Get the file ID for the corresponding function or template.\n    #[must_use]\n    pub fn file_id(&self) -> &Option<FileID> {\n        self.parameters.file_id()\n    }\n\n    #[must_use]\n    pub fn definition_type(&self) -> &DefinitionType {\n        &self.definition_type\n    }\n\n    #[must_use]\n    pub fn constants(&self) -> &UsefulConstants {\n        &self.constants\n    }\n\n    /// Returns the parameter data for the corresponding function or template.\n    #[must_use]\n    pub fn parameters(&self) -> &Parameters {\n        &self.parameters\n    }\n\n    /// Returns the variable declaration for the CFG.\n    #[must_use]\n    pub fn declarations(&self) -> &Declarations {\n        &self.declarations\n    }\n\n    /// Returns an iterator over the set of variables defined by the CFG.\n    pub fn variables(&self) -> impl Iterator<Item = &VariableName> {\n        self.declarations.iter().map(|(name, _)| name)\n    }\n\n    /// Returns an iterator over the input signals of the CFG.\n    pub fn input_signals(&self) -> impl Iterator<Item = &VariableName> {\n        use SignalType::*;\n        use VariableType::*;\n        self.declarations.iter().filter_map(|(name, declaration)| {\n            match declaration.variable_type() {\n                Signal(Input, _) => Some(name),\n                _ => None,\n            }\n        })\n    }\n\n    /// Returns an iterator over the output signals of the CFG.\n    pub fn output_signals(&self) -> impl Iterator<Item = &VariableName> {\n        use SignalType::*;\n        use VariableType::*;\n        self.declarations.iter().filter_map(|(name, declaration)| {\n            match declaration.variable_type() {\n                Signal(Output, _) => Some(name),\n                _ => None,\n            }\n        })\n    }\n\n    /// Returns the declaration of the given variable.\n    #[must_use]\n    pub fn get_declaration(&self, name: &VariableName) -> Option<&Declaration> {\n        self.declarations.get_declaration(name)\n    }\n\n    /// Returns the type of the given variable.\n    #[must_use]\n    pub fn get_type(&self, name: &VariableName) -> Option<&VariableType> {\n        self.declarations.get_type(name)\n    }\n\n    /// Returns an iterator over the basic blocks in the CFG. This iterator\n    /// guarantees that if `i` dominates `j`, then `i` comes before `j`.\n    pub fn iter(&self) -> impl Iterator<Item = &BasicBlock> {\n        self.basic_blocks.iter()\n    }\n\n    /// Returns a mutable iterator over the basic blocks in the CFG.\n    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut BasicBlock> {\n        self.basic_blocks.iter_mut()\n    }\n\n    /// Returns the dominators of the given basic block. The basic block `i`\n    /// dominates `j` if any path from the entry point to `j` must contain `i`.\n    /// (Note that this relation is reflexive, so `i` always dominates itself.)\n    #[must_use]\n    pub fn get_dominators(&self, basic_block: &BasicBlock) -> Vec<&BasicBlock> {\n        self.dominator_tree\n            .get_dominators(basic_block.index())\n            .iter()\n            .map(|&i| &self.basic_blocks[i])\n            .collect()\n    }\n\n    /// Returns the immediate dominator of the basic block (that is, the\n    /// predecessor of the node in the CFG dominator tree), if it exists.\n    #[must_use]\n    pub fn get_immediate_dominator(&self, basic_block: &BasicBlock) -> Option<&BasicBlock> {\n        self.dominator_tree\n            .get_immediate_dominator(basic_block.index())\n            .map(|i| &self.basic_blocks[i])\n    }\n\n    /// Get immediate successors of the basic block in the CFG dominator tree.\n    /// (For a definition of the dominator relation, see `CFG::get_dominators`.)\n    #[must_use]\n    pub fn get_dominator_successors(&self, basic_block: &BasicBlock) -> Vec<&BasicBlock> {\n        self.dominator_tree\n            .get_dominator_successors(basic_block.index())\n            .iter()\n            .map(|&i| &self.basic_blocks[i])\n            .collect()\n    }\n\n    /// Returns the dominance frontier of the basic block. The _dominance\n    /// frontier_ of `i` is defined as all basic blocks `j` such that `i`\n    /// dominates an immediate predecessor of `j`, but i does not strictly\n    /// dominate `j`. (`j` is where `i`s dominance ends.)\n    #[must_use]\n    pub fn get_dominance_frontier(&self, basic_block: &BasicBlock) -> Vec<&BasicBlock> {\n        self.dominator_tree\n            .get_dominance_frontier(basic_block.index())\n            .iter()\n            .map(|&i| &self.basic_blocks[i])\n            .collect()\n    }\n\n    /// Returns the predecessors of the given basic block.\n    pub fn get_predecessors(&self, basic_block: &BasicBlock) -> Vec<&BasicBlock> {\n        let mut predecessors = HashSet::new();\n        let mut update = HashSet::from([basic_block.index()]);\n        while !update.is_subset(&predecessors) {\n            predecessors.extend(update.iter().cloned());\n            update = update\n                .iter()\n                .flat_map(|index| {\n                    self.get_basic_block(*index)\n                        .expect(\"in control-flow graph\")\n                        .predecessors()\n                        .iter()\n                        .cloned()\n                })\n                .collect();\n        }\n        // Remove the initial block.\n        predecessors.remove(&basic_block.index());\n        predecessors\n            .iter()\n            .map(|index| self.get_basic_block(*index).expect(\"in control-flow graph\"))\n            .collect::<Vec<_>>()\n    }\n\n    /// Returns the successors of the given basic block.\n    pub fn get_successors(&self, basic_block: &BasicBlock) -> Vec<&BasicBlock> {\n        let mut successors = HashSet::new();\n        let mut update = HashSet::from([basic_block.index()]);\n        while !update.is_subset(&successors) {\n            successors.extend(update.iter().cloned());\n            update = update\n                .iter()\n                .flat_map(|index| {\n                    self.get_basic_block(*index)\n                        .expect(\"in control-flow graph\")\n                        .successors()\n                        .iter()\n                        .cloned()\n                })\n                .collect();\n        }\n        // Remove the initial block.\n        successors.remove(&basic_block.index());\n        successors\n            .iter()\n            .map(|index| self.get_basic_block(*index).expect(\"in control-flow graph\"))\n            .collect::<Vec<_>>()\n    }\n\n    /// Returns all block in the interval [start_block, end_block). That is, all\n    /// successors of the starting block (including the starting block) which\n    /// are also predecessors of the end block.\n    pub fn get_interval(\n        &self,\n        start_block: &BasicBlock,\n        end_block: &BasicBlock,\n    ) -> Vec<&BasicBlock> {\n        // Compute the successors of the start block (including the start block).\n        let mut successors = HashSet::new();\n        let mut update = HashSet::from([start_block.index()]);\n        while !update.is_subset(&successors) {\n            successors.extend(update.iter().cloned());\n            update = update\n                .iter()\n                .flat_map(|index| {\n                    self.get_basic_block(*index)\n                        .expect(\"in control-flow graph\")\n                        .successors()\n                        .iter()\n                        .cloned()\n                })\n                .collect();\n        }\n\n        // Compute the strict predecessors of the end block.\n        let mut predecessors = HashSet::new();\n        let mut update = HashSet::from([end_block.index()]);\n        while !update.is_subset(&predecessors) {\n            predecessors.extend(update.iter().cloned());\n            update = update\n                .iter()\n                .flat_map(|index| {\n                    self.get_basic_block(*index)\n                        .expect(\"in control-flow graph\")\n                        .predecessors()\n                        .iter()\n                        .cloned()\n                })\n                .collect();\n        }\n        predecessors.remove(&end_block.index());\n\n        // Return the basic blocks corresponding to the intersection of the two\n        // sets.\n        successors\n            .intersection(&predecessors)\n            .map(|index| self.get_basic_block(*index).expect(\"in control-flow graph\"))\n            .collect::<Vec<_>>()\n    }\n\n    /// Returns the basic blocks corresponding to the true branch of the\n    /// if-statement at the end of the given header block.\n    ///\n    /// # Panics\n    ///\n    /// This method panics if the given block does not end with an if-statement node.\n    pub fn get_true_branch(&self, header_block: &BasicBlock) -> Vec<&BasicBlock> {\n        use crate::ir::Statement::*;\n        if let Some(IfThenElse { true_index, .. }) = header_block.statements().last() {\n            let start_block = self.get_basic_block(*true_index).expect(\"in control-flow graph\");\n            let end_blocks = self.get_dominance_frontier(start_block);\n\n            if end_blocks.is_empty() {\n                // True and false branches do not join up.\n                let mut result = self.get_successors(start_block);\n                result.push(start_block);\n                result\n            } else {\n                // True and false branches join up at the dominance frontier.\n                let mut result = Vec::new();\n                for end_block in end_blocks {\n                    result.extend(self.get_interval(start_block, end_block))\n                }\n                result\n            }\n        } else {\n            panic!(\"the given header block does not end with an if-statement\");\n        }\n    }\n\n    /// Returns the basic blocks corresponding to the false branch of the\n    /// if-statement at the end of the given header block.\n    ///\n    /// # Panics\n    ///\n    /// This method panics if the given block does not end with an if-statement node.\n    pub fn get_false_branch(&self, header_block: &BasicBlock) -> Vec<&BasicBlock> {\n        use crate::ir::Statement::*;\n        if let Some(IfThenElse { true_index, false_index, .. }) = header_block.statements().last() {\n            if let Some(false_index) = false_index {\n                if self.dominator_tree.get_dominance_frontier(*true_index).contains(false_index) {\n                    // The false branch is empty.\n                    return Vec::new();\n                }\n                let start_block =\n                    self.get_basic_block(*false_index).expect(\"in control-flow graph\");\n                let end_blocks = self.get_dominance_frontier(start_block);\n\n                if end_blocks.is_empty() {\n                    // True and false branches do not join up.\n                    let mut result = self.get_successors(start_block);\n                    result.push(start_block);\n                    result\n                } else {\n                    // True and false branches join up at the dominance frontier.\n                    let mut result = Vec::new();\n                    for end_block in end_blocks {\n                        result.extend(self.get_interval(start_block, end_block))\n                    }\n                    result\n                }\n            } else {\n                Vec::new()\n            }\n        } else {\n            panic!(\"the given header block does not end with an if-statement\");\n        }\n    }\n\n    /// Cache variable use for each node in the CFG.\n    pub(crate) fn cache_variable_use(&mut self) {\n        debug!(\"computing variable use for `{}`\", self.name());\n        for basic_block in self.iter_mut() {\n            basic_block.cache_variable_use();\n        }\n    }\n\n    /// Propagate expression degrees along the CFG.\n    pub(crate) fn propagate_degrees(&mut self) {\n        use Degree::*;\n        debug!(\"propagating expression degrees for `{}`\", self.name());\n        let mut env = DegreeEnvironment::new();\n        for param in self.parameters().iter() {\n            env.set_type(param, &VariableType::Local);\n            if matches!(self.definition_type(), DefinitionType::Function) {\n                // For functions, the parameters may be constants or signals.\n                let range = DegreeRange::new(Constant, Linear);\n                env.set_degree(param, &range);\n            } else {\n                // For templates, the parameters are constants.\n                env.set_degree(param, &Constant.into());\n            }\n        }\n        let mut rerun = true;\n        let start = Instant::now();\n        while rerun {\n            // Rerun degree propagation if a single child node was updated.\n            rerun = false;\n            for basic_block in self.iter_mut() {\n                rerun = rerun || basic_block.propagate_degrees(&mut env);\n            }\n            // Bail out if analysis takes more than 10 seconds.\n            if start.elapsed() > MAX_ANALYSIS_DURATION {\n                debug!(\"failed to propagate degrees within allotted time\");\n                rerun = false;\n            }\n        }\n    }\n\n    /// Propagate constant values along the CFG.\n    pub(crate) fn propagate_values(&mut self) {\n        debug!(\"propagating constant values for `{}`\", self.name());\n        let mut env = ValueEnvironment::new(&self.constants);\n        let mut rerun = true;\n        let start = Instant::now();\n        while rerun {\n            // Rerun value propagation if a single child node was updated.\n            rerun = false;\n            for basic_block in self.iter_mut() {\n                rerun = rerun || basic_block.propagate_values(&mut env);\n            }\n            // Bail out if analysis takes more than 10 seconds.\n            if start.elapsed() > MAX_ANALYSIS_DURATION {\n                debug!(\"failed to propagate values within allotted time\");\n                rerun = false;\n            }\n        }\n    }\n\n    /// Propagate variable types along the CFG.\n    pub(crate) fn propagate_types(&mut self) {\n        debug!(\"propagating variable types for `{}`\", self.name());\n        // Need to clone declarations here since we cannot borrow self both\n        // mutably and immutably.\n        let declarations = self.declarations.clone();\n        for basic_block in self.iter_mut() {\n            basic_block.propagate_types(&declarations);\n        }\n    }\n}\n\nimpl fmt::Debug for Cfg {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        for basic_block in self.iter() {\n            writeln!(\n                f,\n                \"basic block {}, predecessors: {:?}, successors: {:?}\",\n                basic_block.index(),\n                basic_block.predecessors(),\n                basic_block.successors(),\n            )?;\n            write!(f, \"{basic_block:?}\")?;\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "program_structure/src/control_flow_graph/errors.rs",
    "content": "use thiserror::Error;\n\nuse crate::report_code::ReportCode;\nuse crate::report::Report;\nuse crate::file_definition::{FileID, FileLocation};\nuse crate::ir::errors::IRError;\n\n/// Error enum for CFG generation errors.\n#[derive(Debug, Error)]\npub enum CFGError {\n    #[error(\"The variable `{name}` is read before it is declared/written.\")]\n    UndefinedVariableError { name: String, file_id: Option<FileID>, file_location: FileLocation },\n    #[error(\"The variable name `{name}` contains invalid characters.\")]\n    InvalidVariableNameError { name: String, file_id: Option<FileID>, file_location: FileLocation },\n    #[error(\"The declaration of the variable `{name}` shadows a previous declaration.\")]\n    ShadowingVariableWarning {\n        name: String,\n        primary_file_id: Option<FileID>,\n        primary_location: FileLocation,\n        secondary_file_id: Option<FileID>,\n        secondary_location: FileLocation,\n    },\n    #[error(\"Multiple parameters with the same name `{name}` in function or template definition.\")]\n    ParameterNameCollisionError {\n        name: String,\n        file_id: Option<FileID>,\n        file_location: FileLocation,\n    },\n}\n\npub type CFGResult<T> = Result<T, CFGError>;\n\nimpl CFGError {\n    pub fn into_report(self) -> Report {\n        use CFGError::*;\n        match self {\n            UndefinedVariableError { name, file_id, file_location } => {\n                let mut report = Report::error(\n                    format!(\"The variable `{name}` is used before it is defined.\"),\n                    ReportCode::UninitializedSymbolInExpression,\n                );\n                if let Some(file_id) = file_id {\n                    report.add_primary(\n                        file_location,\n                        file_id,\n                        format!(\"The variable `{name}` is first seen here.\"),\n                    );\n                }\n                report\n            }\n            InvalidVariableNameError { name, file_id, file_location } => {\n                let mut report = Report::error(\n                    format!(\"Invalid variable name `{name}`.\"),\n                    ReportCode::ParseFail,\n                );\n                if let Some(file_id) = file_id {\n                    report.add_primary(\n                        file_location,\n                        file_id,\n                        \"This variable name contains invalid characters.\".to_string(),\n                    );\n                }\n                report\n            }\n            ShadowingVariableWarning {\n                name,\n                primary_file_id,\n                primary_location,\n                secondary_file_id,\n                secondary_location,\n            } => {\n                let mut report = Report::warning(\n                    format!(\"Declaration of variable `{name}` shadows previous declaration.\"),\n                    ReportCode::ShadowingVariable,\n                );\n                if let Some(primary_file_id) = primary_file_id {\n                    report.add_primary(\n                        primary_location,\n                        primary_file_id,\n                        \"Shadowing declaration here.\".to_string(),\n                    );\n                }\n                if let Some(secondary_file_id) = secondary_file_id {\n                    report.add_secondary(\n                        secondary_location,\n                        secondary_file_id,\n                        Some(\"Shadowed variable is declared here.\".to_string()),\n                    );\n                }\n                report.add_note(format!(\"Consider renaming the second occurrence of `{name}`.\"));\n                report\n            }\n            ParameterNameCollisionError { name, file_id, file_location } => {\n                let mut report = Report::warning(\n                    format!(\"Parameter `{name}` declared multiple times.\"),\n                    ReportCode::ParameterNameCollision,\n                );\n                if let Some(file_id) = file_id {\n                    report.add_primary(\n                        file_location,\n                        file_id,\n                        \"Parameters declared here.\".to_string(),\n                    );\n                }\n                report.add_note(format!(\"Rename the second occurrence of `{name}`.\"));\n                report\n            }\n        }\n    }\n}\n\nimpl From<IRError> for CFGError {\n    fn from(error: IRError) -> CFGError {\n        match error {\n            IRError::UndefinedVariableError { name, file_id, file_location } => {\n                CFGError::UndefinedVariableError { name, file_id, file_location }\n            }\n            IRError::InvalidVariableNameError { name, file_id, file_location } => {\n                CFGError::InvalidVariableNameError { name, file_id, file_location }\n            }\n        }\n    }\n}\n\nimpl From<CFGError> for Report {\n    fn from(error: CFGError) -> Report {\n        error.into_report()\n    }\n}\n"
  },
  {
    "path": "program_structure/src/control_flow_graph/lifting.rs",
    "content": "use log::{debug, trace};\nuse std::collections::HashSet;\n\nuse crate::ast;\nuse crate::ast::Definition;\n\nuse crate::constants::{UsefulConstants, Curve};\nuse crate::function_data::FunctionData;\nuse crate::ir;\nuse crate::ir::declarations::{Declaration, Declarations};\nuse crate::ir::errors::IRResult;\nuse crate::ir::lifting::{LiftingEnvironment, TryLift};\nuse crate::ir::VariableType;\n\nuse crate::report::ReportCollection;\nuse crate::nonempty_vec::NonEmptyVec;\nuse crate::ssa::dominator_tree::DominatorTree;\nuse crate::template_data::TemplateData;\n\nuse super::basic_block::BasicBlock;\nuse super::cfg::DefinitionType;\nuse super::errors::{CFGError, CFGResult};\nuse super::parameters::Parameters;\nuse super::unique_vars::ensure_unique_variables;\nuse super::Cfg;\n\ntype Index = usize;\ntype IndexSet = HashSet<Index>;\ntype BasicBlockVec = NonEmptyVec<BasicBlock>;\n\n/// This is a high level trait which simply wraps the implementation provided by\n/// `TryLift`. We need to pass the prime to the CFG here, to be able to do value\n/// propagation when converting to SSA.\npub trait IntoCfg {\n    fn into_cfg(self, curve: &Curve, reports: &mut ReportCollection) -> CFGResult<Cfg>;\n}\n\nimpl<T> IntoCfg for T\nwhere\n    T: TryLift<UsefulConstants, IR = Cfg, Error = CFGError>,\n{\n    fn into_cfg(self, curve: &Curve, reports: &mut ReportCollection) -> CFGResult<Cfg> {\n        let constants = UsefulConstants::new(curve);\n        self.try_lift(constants, reports)\n    }\n}\n\nimpl From<&Parameters> for LiftingEnvironment {\n    fn from(params: &Parameters) -> LiftingEnvironment {\n        let mut env = LiftingEnvironment::new();\n        for name in params.iter() {\n            let declaration = Declaration::new(\n                name,\n                &VariableType::Local,\n                &Vec::new(),\n                params.file_id(),\n                params.file_location(),\n            );\n            env.add_declaration(&declaration);\n        }\n        env\n    }\n}\n\nimpl TryLift<UsefulConstants> for &TemplateData {\n    type IR = Cfg;\n    type Error = CFGError;\n\n    fn try_lift(\n        &self,\n        constants: UsefulConstants,\n        reports: &mut ReportCollection,\n    ) -> CFGResult<Cfg> {\n        let name = self.get_name().to_string();\n        let parameters = Parameters::from(*self);\n        let body = self.get_body().clone();\n        let definition_type = if self.is_custom_gate() {\n            DefinitionType::CustomTemplate\n        } else {\n            DefinitionType::Template\n        };\n        debug!(\"building CFG for template `{name}`\");\n        try_lift_impl(name, definition_type, constants, parameters, body, reports)\n    }\n}\n\nimpl TryLift<UsefulConstants> for &FunctionData {\n    type IR = Cfg;\n    type Error = CFGError;\n\n    fn try_lift(\n        &self,\n        constants: UsefulConstants,\n        reports: &mut ReportCollection,\n    ) -> CFGResult<Cfg> {\n        let name = self.get_name().to_string();\n        let parameters = Parameters::from(*self);\n        let body = self.get_body().clone();\n\n        debug!(\"building CFG for function `{name}`\");\n        try_lift_impl(name, DefinitionType::Function, constants, parameters, body, reports)\n    }\n}\n\nimpl TryLift<UsefulConstants> for Definition {\n    type IR = Cfg;\n    type Error = CFGError;\n\n    fn try_lift(\n        &self,\n        constants: UsefulConstants,\n        reports: &mut ReportCollection,\n    ) -> CFGResult<Cfg> {\n        match self {\n            Definition::Template { name, body, is_custom_gate, .. } => {\n                debug!(\"building CFG for template `{name}`\");\n                let definition_type = if *is_custom_gate {\n                    DefinitionType::CustomTemplate\n                } else {\n                    DefinitionType::Template\n                };\n                try_lift_impl(\n                    name.clone(),\n                    definition_type,\n                    constants,\n                    self.into(),\n                    body.clone(),\n                    reports,\n                )\n            }\n            Definition::Function { name, body, .. } => {\n                debug!(\"building CFG for function `{name}`\");\n                try_lift_impl(\n                    name.clone(),\n                    DefinitionType::Function,\n                    constants,\n                    self.into(),\n                    body.clone(),\n                    reports,\n                )\n            }\n        }\n    }\n}\n\nfn try_lift_impl(\n    name: String,\n    definition_type: DefinitionType,\n    constants: UsefulConstants,\n    parameters: Parameters,\n    mut body: ast::Statement,\n    reports: &mut ReportCollection,\n) -> CFGResult<Cfg> {\n    // 1. Ensure that variable names are globally unique before converting to basic blocks.\n    ensure_unique_variables(&mut body, &parameters, reports)?;\n\n    // 2. Convert template AST to CFG and compute dominator tree.\n    let mut env = LiftingEnvironment::from(&parameters);\n    let basic_blocks = build_basic_blocks(&body, &mut env, reports)?;\n    let dominator_tree = DominatorTree::new(&basic_blocks);\n    let declarations = Declarations::from(env);\n    let mut cfg = Cfg::new(\n        name,\n        constants,\n        definition_type,\n        parameters,\n        declarations,\n        basic_blocks,\n        dominator_tree,\n    );\n\n    // 3. Propagate metadata to all child nodes. Since determining variable use\n    // requires that variable types are available, type propagation must run\n    // before caching variable use.\n    //\n    // Note that the current implementations of value and degree propagation\n    // only make sense in SSA form.\n    cfg.propagate_types();\n    cfg.cache_variable_use();\n\n    Ok(cfg)\n}\n\n/// This function generates a vector of basic blocks containing `ir::Statement`s\n/// from a function or template body. The vector is guaranteed to be non-empty,\n/// and the first block (with index 0) will always be the entry block.\npub(crate) fn build_basic_blocks(\n    body: &ast::Statement,\n    env: &mut LiftingEnvironment,\n    reports: &mut ReportCollection,\n) -> IRResult<Vec<BasicBlock>> {\n    assert!(matches!(body, ast::Statement::Block { .. }));\n\n    let meta = body.get_meta().try_lift((), reports)?;\n    let mut basic_blocks = BasicBlockVec::new(BasicBlock::new(meta, Index::default(), 0));\n    visit_statement(body, 0, env, reports, &mut basic_blocks)?;\n    Ok(basic_blocks.into())\n}\n\n/// Update the CFG with the current statement. This implementation assumes that\n/// all control-flow statement bodies are wrapped by a `Block` statement. Blocks\n/// are finalized and the current block (i.e. last block) is updated when:\n///\n///   1. The current statement is a `While` statement. An `IfThenElse` statement\n///      is added to the current block. The successors of the if-statement will be\n///      the while-statement body and the while-statement successor (if any).\n///   2. The current statement is an `IfThenElse` statement. The current statement\n///      is added to the current block. The successors of the if-statement will\n///      be the if-case body and else-case body (if any).\n///\n/// The function returns the predecessors of the next block in the CFG.\nfn visit_statement(\n    stmt: &ast::Statement,\n    loop_depth: usize,\n    env: &mut LiftingEnvironment,\n    reports: &mut ReportCollection,\n    basic_blocks: &mut BasicBlockVec,\n) -> IRResult<IndexSet> {\n    let current_index = basic_blocks.last().index();\n\n    match stmt {\n        ast::Statement::InitializationBlock { initializations: stmts, .. } => {\n            // Add each statement in the initialization block to the current\n            // block. Since initialization blocks only contain declarations and\n            // substitutions, we do not need to track predecessors here.\n            trace!(\"entering initialization block statement\");\n            for stmt in stmts {\n                assert!(visit_statement(stmt, loop_depth, env, reports, basic_blocks)?.is_empty());\n            }\n            trace!(\"leaving initialization block statement\");\n            Ok(HashSet::new())\n        }\n        ast::Statement::Block { stmts, .. } => {\n            // Add each statement in the basic block to the current block. If a\n            // call to `visit_statement` completes a basic block and returns a set\n            // of predecessors for the next block, we create a new block before\n            // continuing.\n            trace!(\"entering block statement\");\n\n            let mut pred_set = IndexSet::new();\n            for stmt in stmts {\n                if !pred_set.is_empty() {\n                    let meta = stmt.get_meta().try_lift((), reports)?;\n                    complete_basic_block(basic_blocks, meta, &pred_set, loop_depth);\n                }\n                pred_set = visit_statement(stmt, loop_depth, env, reports, basic_blocks)?;\n            }\n            trace!(\"leaving block statement (predecessors: {:?})\", pred_set);\n\n            // If the last statement of the block is a control-flow statement,\n            // `pred_set` will be non-empty. Otherwise it will be empty.\n            Ok(pred_set)\n        }\n        ast::Statement::While { meta, cond, stmt: while_body, .. } => {\n            let pred_set = HashSet::from([current_index]);\n            complete_basic_block(basic_blocks, meta.try_lift((), reports)?, &pred_set, loop_depth);\n\n            // While statements are translated into a loop head with a single\n            // if-statement, and a loop body containing the while-statement\n            // body. The index of the loop header will be `current_index + 1`,\n            // and the index of the loop body will be `current_index + 2`.\n            trace!(\"appending statement `if {cond}` to basic block {current_index}\");\n            basic_blocks.last_mut().append_statement(ir::Statement::IfThenElse {\n                meta: meta.try_lift((), reports)?,\n                cond: cond.try_lift((), reports)?,\n                true_index: current_index + 2,\n                false_index: None, // May be updated later.\n            });\n            let header_index = current_index + 1;\n\n            // Visit the while-statement body.\n            let meta = while_body.get_meta().try_lift((), reports)?;\n            let pred_set = HashSet::from([header_index]);\n            complete_basic_block(basic_blocks, meta, &pred_set, loop_depth + 1);\n\n            trace!(\"visiting while body\");\n            let mut pred_set =\n                visit_statement(while_body, loop_depth + 1, env, reports, basic_blocks)?;\n            // The returned predecessor set will be empty if the last statement\n            // of the body is not a conditional. In this case we need to add the\n            // last block of the body to complete the corresponding block.\n            if pred_set.is_empty() {\n                pred_set.insert(basic_blocks.last().index());\n            }\n            // The loop header is the successor of all blocks in `pred_set`.\n            trace!(\"adding predecessors {:?} to loop header {header_index}\", pred_set);\n            for i in pred_set {\n                basic_blocks[i].add_successor(header_index);\n                basic_blocks[header_index].add_predecessor(i);\n            }\n\n            // The next block (if any) will be the false branch and a successor\n            // of the loop header.\n            Ok(HashSet::from([header_index]))\n        }\n        ast::Statement::IfThenElse { meta, cond, if_case, else_case, .. } => {\n            trace!(\"appending statement `if {cond}` to basic block {current_index}\");\n            basic_blocks.last_mut().append_statement(ir::Statement::IfThenElse {\n                meta: meta.try_lift((), reports)?,\n                cond: cond.try_lift((), reports)?,\n                true_index: current_index + 1,\n                false_index: None, // May be updated later.\n            });\n\n            // Visit the if-case body.\n            let meta = if_case.get_meta().try_lift((), reports)?;\n            let pred_set = HashSet::from([current_index]);\n            complete_basic_block(basic_blocks, meta, &pred_set, loop_depth);\n\n            trace!(\"visiting true if-statement branch\");\n            let mut if_pred_set = visit_statement(if_case, loop_depth, env, reports, basic_blocks)?;\n            // The returned predecessor set will be empty if the last statement\n            // of the body is not a conditional. In this case we need to add the\n            // last block of the body to complete the corresponding block.\n            if if_pred_set.is_empty() {\n                if_pred_set.insert(basic_blocks.last().index());\n            }\n\n            // Visit the else-case body.\n            if let Some(else_case) = else_case {\n                trace!(\"visiting false if-statement branch\");\n                let meta = else_case.get_meta().try_lift((), reports)?;\n                let pred_set = HashSet::from([current_index]);\n                complete_basic_block(basic_blocks, meta, &pred_set, loop_depth);\n\n                let mut else_pred_set =\n                    visit_statement(else_case, loop_depth, env, reports, basic_blocks)?;\n                // The returned predecessor set will be empty if the last statement\n                // of the body is not a conditional. In this case we need to add the\n                // last block of the body to complete the corresponding block.\n                if else_pred_set.is_empty() {\n                    else_pred_set.insert(basic_blocks.last().index());\n                }\n                Ok(if_pred_set.union(&else_pred_set).cloned().collect())\n            } else {\n                if_pred_set.insert(current_index);\n                Ok(if_pred_set)\n            }\n        }\n        ast::Statement::Declaration { meta, name, xtype, dimensions, .. } => {\n            // Declarations are also tracked by the CFG header.\n            trace!(\"appending `{stmt}` to basic block {current_index}\");\n            env.add_declaration(&Declaration::new(\n                &name.try_lift(meta, reports)?,\n                &xtype.try_lift((), reports)?,\n                &dimensions\n                    .iter()\n                    .map(|size| size.try_lift((), reports))\n                    .collect::<IRResult<Vec<_>>>()?,\n                &meta.file_id,\n                &meta.location,\n            ));\n            basic_blocks.last_mut().append_statement(stmt.try_lift((), reports)?);\n            Ok(HashSet::new())\n        }\n        _ => {\n            trace!(\"appending `{stmt}` to basic block {current_index}\");\n            basic_blocks.last_mut().append_statement(stmt.try_lift((), reports)?);\n            Ok(HashSet::new())\n        }\n    }\n}\n\n/// Complete the current (i.e. last) basic block and add a new basic block to\n/// the vector with the given `meta`, and `pred_set` as predecessors. Update all\n/// predecessors adding the new block as a successor.\n///\n/// If the final statement of the predecessor block is a control-flow statement,\n/// and the new block is not the true branch target of the statement, the new\n/// block is added as the false branch target.\nfn complete_basic_block(\n    basic_blocks: &mut BasicBlockVec,\n    meta: ir::Meta,\n    pred_set: &IndexSet,\n    loop_depth: usize,\n) {\n    use ir::Statement::*;\n    trace!(\"finalizing basic block {}\", basic_blocks.last().index());\n    let j = basic_blocks.len();\n    basic_blocks.push(BasicBlock::new(meta, j, loop_depth));\n    for i in pred_set {\n        basic_blocks[i].add_successor(j);\n        basic_blocks[j].add_predecessor(*i);\n\n        // If the final statement `S` of block `i` is a control flow statement,\n        // and `j` is not the true branch of `S`, update the false branch of `S`\n        // to `j`.\n        if let Some(IfThenElse { cond, true_index, false_index, .. }) =\n            basic_blocks[i].statements_mut().last_mut()\n        {\n            if j != *true_index && false_index.is_none() {\n                trace!(\"updating false branch target of `if {cond}`\");\n                *false_index = Some(j)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "program_structure/src/control_flow_graph/mod.rs",
    "content": "pub mod basic_block;\npub mod errors;\npub mod parameters;\n\nmod cfg;\nmod lifting;\nmod ssa_impl;\nmod unique_vars;\n\npub use basic_block::BasicBlock;\npub use cfg::{Cfg, DefinitionType, Index};\npub use lifting::IntoCfg;\n"
  },
  {
    "path": "program_structure/src/control_flow_graph/parameters.rs",
    "content": "use crate::ast::Definition;\nuse crate::file_definition::{FileID, FileLocation};\nuse crate::function_data::FunctionData;\nuse crate::template_data::TemplateData;\n\nuse crate::ir::VariableName;\n\npub struct Parameters {\n    param_names: Vec<VariableName>,\n    file_id: Option<FileID>,\n    file_location: FileLocation,\n}\n\nimpl Parameters {\n    #[must_use]\n    pub fn new(\n        param_names: &[String],\n        file_id: Option<FileID>,\n        file_location: FileLocation,\n    ) -> Parameters {\n        Parameters {\n            param_names: param_names.iter().map(VariableName::from_string).collect(),\n            file_id,\n            file_location,\n        }\n    }\n\n    #[must_use]\n    pub fn file_id(&self) -> &Option<FileID> {\n        &self.file_id\n    }\n\n    #[must_use]\n    pub fn file_location(&self) -> &FileLocation {\n        &self.file_location\n    }\n\n    #[must_use]\n    pub fn len(&self) -> usize {\n        self.param_names.len()\n    }\n\n    #[must_use]\n    pub fn is_empty(&self) -> bool {\n        self.len() == 0\n    }\n\n    pub fn iter(&self) -> impl Iterator<Item = &VariableName> {\n        self.param_names.iter()\n    }\n\n    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut VariableName> {\n        self.param_names.iter_mut()\n    }\n\n    pub fn contains(&self, param_name: &VariableName) -> bool {\n        self.param_names.contains(param_name)\n    }\n}\n\nimpl From<&FunctionData> for Parameters {\n    fn from(function: &FunctionData) -> Parameters {\n        Parameters::new(\n            function.get_name_of_params(),\n            Some(function.get_file_id()),\n            function.get_param_location(),\n        )\n    }\n}\n\nimpl From<&TemplateData> for Parameters {\n    fn from(template: &TemplateData) -> Parameters {\n        Parameters::new(\n            template.get_name_of_params(),\n            Some(template.get_file_id()),\n            template.get_param_location(),\n        )\n    }\n}\n\nimpl From<&Definition> for Parameters {\n    fn from(definition: &Definition) -> Parameters {\n        match definition {\n            Definition::Function { meta, args, arg_location, .. }\n            | Definition::Template { meta, args, arg_location, .. } => {\n                Parameters::new(args, meta.file_id, arg_location.clone())\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "program_structure/src/control_flow_graph/ssa_impl.rs",
    "content": "use log::{debug, trace, warn};\nuse std::collections::HashSet;\nuse std::convert::TryInto;\nuse std::ops::Range;\n\nuse crate::environment::VarEnvironment;\nuse crate::ir::declarations::{Declaration, Declarations};\nuse crate::ir::variable_meta::VariableMeta;\nuse crate::ir::*;\nuse crate::ssa::errors::*;\nuse crate::ssa::traits::*;\n\nuse super::basic_block::BasicBlock;\nuse super::parameters::Parameters;\n\ntype Version = usize;\n\npub struct Config {}\n\nimpl SSAConfig for Config {\n    type Version = Version;\n    type Variable = VariableName;\n    type Environment = Environment;\n    type Statement = Statement;\n    type BasicBlock = BasicBlock;\n}\n\n#[derive(Clone)]\n/// A type which tracks variable metadata relevant for SSA.\npub struct Environment {\n    /// Tracks the current scoped version of each variable. This is scoped to\n    /// ensure that versions are updated when a variable goes out of scope.\n    scoped_versions: VarEnvironment<Version>,\n    /// Tracks the maximum version seen of each variable. This is not scoped to\n    /// ensure that we do not apply the same version to different occurrences of\n    /// the same variable names.\n    global_versions: VarEnvironment<Version>,\n    /// Tracks declared local variables, components, and signals to ensure that\n    /// we know if a variable use represents a variable, signal, or component.\n    declarations: Declarations,\n}\n\nimpl Environment {\n    /// Returns a new environment initialized with the parameters of the template or function.\n    pub fn new(parameters: &Parameters, declarations: &Declarations) -> Environment {\n        let mut env = Environment {\n            scoped_versions: VarEnvironment::new(),\n            global_versions: VarEnvironment::new(),\n            declarations: declarations.clone(),\n        };\n        for name in parameters.iter() {\n            env.get_next_version(name);\n        }\n        env\n    }\n\n    /// Gets the current (scoped) version of the variable.\n    pub fn get_current_version(&self, name: &VariableName) -> Option<Version> {\n        // Need to use format to include the suffix.\n        let name = format!(\"{:?}\", name.without_version());\n        self.scoped_versions.get_variable(&name).cloned()\n    }\n\n    /// Gets the range of versions seen for the variable.\n    pub fn get_version_range(&self, name: &VariableName) -> Option<Range<Version>> {\n        // Need to use format to include the suffix.\n        let name = format!(\"{:?}\", name.without_version());\n        self.global_versions.get_variable(&name).map(|max| 0..(max + 1))\n    }\n\n    /// Gets the version to apply for a newly assigned variable.\n    fn get_next_version(&mut self, name: &VariableName) -> Version {\n        // Need to use format to include the suffix.\n        let name = format!(\"{:?}\", name.without_version());\n        let version = match self.global_versions.get_variable(&name) {\n            // The variable has not been seen before. This is version 0 of the variable.\n            None => 0,\n            // The variable has been seen before. The version needs to be increased by 1.\n            Some(version) => version + 1,\n        };\n        self.global_versions.add_variable(&name, version);\n        self.scoped_versions.add_variable(&name, version);\n        version\n    }\n\n    /// Returns true if the given name is a local variable.\n    fn is_local(&self, name: &VariableName) -> bool {\n        matches!(self.declarations.get_type(name), Some(VariableType::Local))\n    }\n}\n\nimpl SSAEnvironment for Environment {\n    // Enter variable scope.\n    fn add_variable_scope(&mut self) {\n        self.scoped_versions.add_variable_block();\n    }\n\n    // Leave variable scope.\n    fn remove_variable_scope(&mut self) {\n        self.scoped_versions.remove_variable_block();\n    }\n}\n\nimpl SSABasicBlock<Config> for BasicBlock {\n    fn prepend_statement(&mut self, stmt: Statement) {\n        self.prepend_statement(stmt);\n    }\n\n    fn statements<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Statement> + 'a> {\n        Box::new(self.iter())\n    }\n\n    fn statements_mut<'a>(&'a mut self) -> Box<dyn Iterator<Item = &'a mut Statement> + 'a> {\n        Box::new(self.iter_mut())\n    }\n}\n\nimpl SSAStatement<Config> for Statement {\n    fn variables_written(&self) -> HashSet<VariableName> {\n        VariableMeta::locals_written(self).iter().map(|var_use| var_use.name()).cloned().collect()\n    }\n\n    fn new_phi_statement(name: &VariableName, env: &Environment) -> Self {\n        use AssignOp::*;\n        use Expression::*;\n        use Statement::*;\n        let phi = Phi {\n            // We have no location for this statement.\n            meta: Meta::default(),\n            // Phi expression arguments are added later.\n            args: Vec::new(),\n        };\n        let mut stmt = Substitution {\n            meta: Meta::default(),\n            // Variable name is versioned later.\n            var: name.without_version(),\n            op: AssignLocalOrComponent,\n            rhe: phi,\n        };\n        // We need to update the node metadata to have a current view of\n        // variable use.\n        stmt.propagate_types(&env.declarations);\n        stmt.cache_variable_use();\n        stmt\n    }\n\n    fn is_phi_statement(&self) -> bool {\n        use Expression::*;\n        use Statement::*;\n        matches!(self, Substitution { rhe: Phi { .. }, .. })\n    }\n\n    fn is_phi_statement_for(&self, name: &VariableName) -> bool {\n        use Expression::*;\n        use Statement::*;\n        match self {\n            Substitution { var, rhe: Phi { .. }, .. } => var == name,\n            _ => false,\n        }\n    }\n\n    fn ensure_phi_argument(&mut self, env: &Environment) {\n        use Expression::*;\n        use Statement::*;\n        match self {\n            // If this is a phi statement we ensure that the RHS contains the\n            // variable version from the given SSA environment.\n            Substitution { var: name, rhe: Phi { args, .. }, .. } => {\n                trace!(\"phi statement for variable `{name}` found\");\n                // If the environment knows about the variable, we ensure that\n                // the versioned variable occurs as an argument to the RHS.\n                if let Some(env_version) = env.get_current_version(name) {\n                    // If the argument list does not contain the current version of the variable we add it.\n                    if args.iter().any(|arg|\n                        matches!( arg.version(), &Some(arg_version) if arg_version == env_version)\n                    ) {\n                        return;\n                    }\n                    args.push(name.with_version(env_version));\n                    self.propagate_types(&env.declarations);\n                    self.cache_variable_use();\n                }\n            }\n            // If this is not a phi statement we panic.\n            _ => panic!(\"expected phi statement\"),\n        }\n    }\n\n    fn insert_ssa_variables(&mut self, env: &mut Environment) -> SSAResult<()> {\n        debug!(\"converting `{self:?}` to SSA\");\n        use Statement::*;\n        let result = match self {\n            Declaration { dimensions, .. } => {\n                // Since at this point we still don't know the version range for\n                // the declared variable we treat declarations in a later pass.\n                for size in dimensions {\n                    visit_expression(size, env)?;\n                }\n                Ok(())\n            }\n            Substitution { var, rhe, .. } => {\n                assert!(var.version().is_none());\n                visit_expression(rhe, env)?;\n                // If this is a variable assignment we need to version the variable.\n                // TODO: We should maybe treat undeclared variables as local variables.\n                if env.is_local(var) {\n                    // If this is the first assignment to the variable we set the version to 0,\n                    // otherwise we increase the version by one.\n                    let version = env.get_next_version(var);\n                    trace!(\n                        \"replacing (written) variable `{var:?}` with SSA variable `{var:?}.{version}`\"\n                    );\n                    *var = var.with_version(version);\n                }\n                Ok(())\n            }\n            ConstraintEquality { lhe, rhe, .. } => {\n                visit_expression(lhe, env)?;\n                visit_expression(rhe, env)\n            }\n            LogCall { args, .. } => {\n                use LogArgument::*;\n                for arg in args {\n                    if let Expr(value) = arg {\n                        visit_expression(value, env)?;\n                    }\n                }\n                Ok(())\n            }\n            IfThenElse { cond, .. } => visit_expression(cond, env),\n            Return { value, .. } => visit_expression(value, env),\n            Assert { arg, .. } => visit_expression(arg, env),\n        };\n        // We need to update the node metadata to have a current view of\n        // variable use.\n        self.propagate_types(&env.declarations);\n        self.cache_variable_use();\n        result\n    }\n}\n\n/// Replaces each occurrence of the variable `v` with a versioned SSA variable `v.n`.\n/// Signals and components are not touched.\nfn visit_expression(expr: &mut Expression, env: &mut Environment) -> SSAResult<()> {\n    use Expression::*;\n    match expr {\n        // Variables are updated with the corresponding SSA version.\n        Variable { meta, name, .. } => {\n            assert!(name.version().is_none(), \"variable already converted to SSA form\");\n            // Ignore declared signals and components, and undeclared variables.\n            // TODO: We should maybe treat undeclared variables as local variables.\n            if !env.is_local(name) {\n                return Ok(());\n            }\n            match env.get_current_version(name) {\n                Some(version) => {\n                    trace!(\n                        \"replacing (read) variable `{name:?}` with SSA variable `{name:?}.{version}`\"\n                    );\n                    *name = name.with_version(version);\n                    Ok(())\n                }\n                None => {\n                    // TODO: Handle undeclared variables more gracefully.\n                    warn!(\"failed to convert undeclared variable `{name:?}` to SSA\");\n                    Err(SSAError::UndefinedVariableError {\n                        name: name.to_string(),\n                        file_id: meta.file_id(),\n                        location: meta.file_location(),\n                    })\n                }\n            }\n        }\n        // Local array accesses are updated with the corresponding SSA version.\n        Access { meta, var, access } => {\n            for access in access {\n                if let AccessType::ArrayAccess(index) = access {\n                    visit_expression(index, env)?;\n                }\n            }\n            // Ignore declared signals and components, and undeclared variables.\n            if !env.is_local(var) {\n                return Ok(());\n            }\n            assert!(var.version().is_none(), \"variable already converted to SSA form\");\n            match env.get_current_version(var) {\n                Some(version) => {\n                    trace!(\n                        \"replacing (read) variable `{var:?}` with SSA variable `{var:?}.{version}`\"\n                    );\n                    *var = var.with_version(version);\n                    Ok(())\n                }\n                None => {\n                    // TODO: Handle undeclared variables more gracefully.\n                    warn!(\"failed to convert undeclared variable `{var:?}` to SSA\");\n                    Err(SSAError::UndefinedVariableError {\n                        name: var.to_string(),\n                        file_id: meta.file_id(),\n                        location: meta.file_location(),\n                    })\n                }\n            }\n        }\n        Update { var, access, rhe, .. } => {\n            visit_expression(rhe, env)?;\n            for access in access {\n                if let AccessType::ArrayAccess(index) = access {\n                    visit_expression(index, env)?;\n                }\n            }\n            // Ignore declared signals and components, and undeclared variables.\n            if !env.is_local(var) {\n                return Ok(());\n            }\n            assert!(var.version().is_none(), \"variable already converted to SSA form\");\n            match env.get_current_version(var) {\n                Some(version) => {\n                    trace!(\n                        \"replacing (read) variable `{var:?}` with SSA variable `{var:?}.{version}`\"\n                    );\n                    *var = var.with_version(version);\n                    Ok(())\n                }\n                None => {\n                    // This is the first assignment to an array. Add the\n                    // variable to the environment and get the first version.\n                    let version = env.get_next_version(var);\n                    trace!(\n                        \"replacing (read) variable `{var:?}` with SSA variable `{var:?}.{version}`\"\n                    );\n                    *var = var.with_version(version);\n                    Ok(())\n                }\n            }\n        }\n        // For all other expression types we simply recurse into their children.\n        PrefixOp { rhe, .. } => visit_expression(rhe, env),\n        InfixOp { lhe, rhe, .. } => {\n            visit_expression(lhe, env)?;\n            visit_expression(rhe, env)\n        }\n        SwitchOp { cond, if_true, if_false, .. } => {\n            visit_expression(cond, env)?;\n            visit_expression(if_true, env)?;\n            visit_expression(if_false, env)\n        }\n        Call { args, .. } => {\n            for arg in args {\n                visit_expression(arg, env)?;\n            }\n            Ok(())\n        }\n        InlineArray { values, .. } => {\n            for value in values {\n                visit_expression(value, env)?;\n            }\n            Ok(())\n        }\n        // phi expression arguments are updated in a later pass.\n        Phi { .. } | Number(_, _) => Ok(()),\n    }\n}\n\n/// Add each version of each variable to the corresponding declaration statement.\n/// Returns a `Declarations` structure containing all declared variables in the\n/// CFG.\n#[must_use]\npub fn update_declarations(\n    basic_blocks: &mut Vec<BasicBlock>,\n    parameters: &Parameters,\n    env: &Environment,\n) -> Declarations {\n    let mut versioned_declarations = Declarations::new();\n    for name in parameters.iter() {\n        // Since parameters are not considered immutable we must assume that\n        // they may be updated (and hence occur as different versions)\n        // throughout the function/template.\n        for version in env.get_version_range(name).expect(\"variable in environment\") {\n            trace!(\"adding declaration for variable `{}`\", name.with_version(version));\n            versioned_declarations.add_declaration(&Declaration::new(\n                &name.with_version(version),\n                &VariableType::Local,\n                &Vec::new(),\n                parameters.file_id(),\n                parameters.file_location(),\n            ));\n        }\n    }\n    for basic_block in basic_blocks {\n        for stmt in basic_block.iter_mut() {\n            if let Statement::Declaration { meta, names, var_type, dimensions } = stmt {\n                let name = names.first();\n                assert!(names.len() == 1 && name.version().is_none());\n\n                if matches!(var_type, VariableType::Local) {\n                    let mut versions = env\n                        .get_version_range(name)\n                        .unwrap_or(0..1) // This will happen if the variable is not assigned to.\n                        .collect::<Vec<_>>();\n                    versions.sort_unstable();\n\n                    // Add a new declaration for each version of the local variable.\n                    let mut versioned_names = Vec::new();\n                    for version in versions {\n                        trace!(\"adding declaration for variable `{}`\", name.with_version(version));\n                        versioned_names.push(name.with_version(version));\n                        versioned_declarations.add_declaration(&Declaration::new(\n                            &name.with_version(version),\n                            var_type,\n                            dimensions,\n                            &meta.file_id(),\n                            &meta.file_location(),\n                        ));\n                    }\n                    // Update declaration statement with versioned variable names.\n                    *names = versioned_names.try_into().expect(\"variable in environment\");\n                } else {\n                    // Declarations of signals and components are just copied over.\n                    trace!(\"adding declaration for variable `{}`\", names.first());\n                    versioned_declarations.add_declaration(&Declaration::new(\n                        name,\n                        var_type,\n                        dimensions,\n                        &meta.file_id(),\n                        &meta.file_location(),\n                    ));\n                }\n            }\n        }\n    }\n    versioned_declarations\n}\n"
  },
  {
    "path": "program_structure/src/control_flow_graph/unique_vars.rs",
    "content": "use log::trace;\nuse std::convert::{TryFrom, TryInto};\n\nuse super::errors::{CFGError, CFGResult};\nuse super::parameters::Parameters;\n\nuse crate::ast::{Access, Expression, Meta, Statement, LogArgument};\nuse crate::environment::VarEnvironment;\nuse crate::report::{Report, ReportCollection};\nuse crate::file_definition::{FileID, FileLocation};\n\ntype Version = usize;\n\n// Location of the last seen declaration of a variable.\nstruct Declaration {\n    file_id: Option<FileID>,\n    file_location: FileLocation,\n}\n\nimpl Declaration {\n    fn new(file_id: Option<FileID>, file_location: FileLocation) -> Declaration {\n        Declaration { file_id, file_location }\n    }\n\n    fn file_id(&self) -> Option<FileID> {\n        self.file_id\n    }\n\n    fn file_location(&self) -> FileLocation {\n        self.file_location.clone()\n    }\n}\n\nstruct DeclarationEnvironment {\n    // Tracks the last seen declaration of each variable. This is scoped to\n    // ensure that we know when a new declaration shadows a previous declaration.\n    declarations: VarEnvironment<Declaration>,\n    // Tracks the current scoped version of each variable. This is scoped to\n    // ensure that versions are updated when a variable goes out of scope.\n    scoped_versions: VarEnvironment<Version>,\n    // Tracks the maximum version seen of each variable. This is not scoped to\n    // ensure that we do not apply the same version to different occurrences of\n    // the same variable names. (See case 2 below.) If the variable is unique\n    // the maximum version is `None` (i.e. the variable is not versioned).\n    global_versions: VarEnvironment<Option<Version>>,\n}\n\nimpl DeclarationEnvironment {\n    pub fn new() -> DeclarationEnvironment {\n        DeclarationEnvironment {\n            declarations: VarEnvironment::new(),\n            scoped_versions: VarEnvironment::new(),\n            global_versions: VarEnvironment::new(),\n        }\n    }\n\n    // Get the last declaration seen for the given variable.\n    pub fn get_declaration(&self, name: &str) -> Option<&Declaration> {\n        self.declarations.get_variable(name)\n    }\n\n    // Add a declaration for the given variable. Returns the version to apply for the declared variable.\n    pub fn add_declaration(\n        &mut self,\n        name: &str,\n        file_id: Option<FileID>,\n        file_location: FileLocation,\n    ) -> Option<Version> {\n        self.declarations.add_variable(name, Declaration::new(file_id, file_location));\n        self.get_next_version(name)\n    }\n\n    // Get the current (scoped) version of the variable.\n    pub fn get_current_version(&self, name: &str) -> Option<&Version> {\n        self.scoped_versions.get_variable(name)\n    }\n\n    // Get the version to apply for a newly declared variable.\n    fn get_next_version(&mut self, name: &str) -> Option<Version> {\n        // Update the global version.\n        let version = match self.global_versions.get_variable(name) {\n            // The variable is not seen before. It does not need to be versioned.\n            None => None,\n            // The variable has been seen exactly once. This declaration needs to be versioned.\n            Some(None) => Some(0),\n            // The variable has been seen more than once. The version needs to be increased by 1.\n            Some(Some(version)) => Some(version + 1),\n        };\n        self.global_versions.add_variable(name, version);\n\n        match version {\n            // The variable does not need to be versioned. Do not update the scoped version.\n            None => None,\n            // The variable needs to be versioned. Update the scoped version.\n            Some(version) => {\n                self.scoped_versions.add_variable(name, version);\n                Some(version)\n            }\n        }\n    }\n\n    // Enter variable scope.\n    pub fn add_variable_block(&mut self) {\n        self.declarations.add_variable_block();\n        self.scoped_versions.add_variable_block();\n    }\n\n    // Leave variable scope.\n    pub fn remove_variable_block(&mut self) {\n        self.declarations.remove_variable_block();\n        self.scoped_versions.remove_variable_block();\n    }\n}\n\nimpl TryFrom<&Parameters> for DeclarationEnvironment {\n    type Error = CFGError;\n\n    fn try_from(params: &Parameters) -> CFGResult<Self> {\n        let mut env = DeclarationEnvironment::new();\n        for name in params.iter() {\n            let file_id = *params.file_id();\n            let file_location = params.file_location().clone();\n            if env.add_declaration(&name.to_string(), file_id, file_location).is_some() {\n                return Err(CFGError::ParameterNameCollisionError {\n                    name: name.to_string(),\n                    file_id: *params.file_id(),\n                    file_location: params.file_location().clone(),\n                });\n            }\n        }\n        Ok(env)\n    }\n}\n\n/// Renames variables to ensure that variable names are globally unique.  This\n/// is done before the CFG is generated to ensure that different variables with\n/// the same names are not identified by mistake.\n///\n/// There are a number of different cases to consider.\n///\n/// 1. The variable `x` has multiple declarations, where (at least) one\n/// declaration of `x` shadows another declaration. E.g.\n///\n/// ```rs\n/// function f(x) {\n///     var y = 1;\n///     if (x < y) {\n///         var x = 3;\n///         y = x;\n///     }\n/// }\n/// ```\n///\n/// In this case, the inner declaration of the variable `x` shadows the outer\n/// declaration and the second occurrence of `x` must be renamed.\n///\n/// 2. The variable `x` has multiple declarations but no declaration of `x`\n/// shadows another declaration. E.g.\n///\n/// ```rs\n/// function g(m) {\n///     var n = 1;\n///     if (m < n) {\n///         var x = 1;\n///         n = x;\n///     } else {\n///         var x = 2;\n///         n = x;\n///     }\n/// }\n/// ```\n///\n/// In this case one of the declared variables still has to be renamed to ensure\n/// global uniqueness.\n///\n/// 3. The variable `x` is only declared once. In this case the variable name is\n/// already unique and `x` should not be renamed.\npub fn ensure_unique_variables(\n    stmt: &mut Statement,\n    param_data: &Parameters,\n    reports: &mut ReportCollection,\n) -> CFGResult<()> {\n    // Ensure that this method is only called on function or template bodies.\n    assert!(matches!(stmt, Statement::Block { .. }));\n\n    let mut env = param_data.try_into()?;\n    visit_statement(stmt, &mut env, reports);\n    Ok(())\n}\n\nfn visit_statement(\n    stmt: &mut Statement,\n    env: &mut DeclarationEnvironment,\n    reports: &mut ReportCollection,\n) {\n    use Statement::*;\n    match stmt {\n        Declaration { meta, name, dimensions, .. } => {\n            trace!(\"visiting declared variable `{name}`\");\n            for size in dimensions {\n                visit_expression(size, env);\n            }\n            // If the current declaration shadows a previous declaration of the same\n            // variable we generate a new report.\n            if let Some(declaration) = env.get_declaration(name) {\n                reports.push(build_report(name, meta, declaration));\n            }\n            match env.add_declaration(name, meta.file_id, meta.file_location()) {\n                // This is a declaration of a previously unseen variable. It should not be versioned.\n                None => {}\n                // This is a declaration of a previously seen variable. It needs to be versioned.\n                Some(version) => {\n                    trace!(\"renaming declared variable `{name}` to `{name}.{version}`\");\n                    // It is a bit hacky to track the variable version as part of the variable name,\n                    // but we do this in order to remain compatible with the original Circom AST.\n                    *name = format!(\"{name}.{version}\");\n                }\n            }\n        }\n        Substitution { var, rhe, access, .. } => {\n            trace!(\"visiting assigned variable '{var}'\");\n            *var = match env.get_current_version(var) {\n                Some(version) => {\n                    trace!(\"renaming assigned shadowing variable `{var}` to `{var}.{version}`\");\n                    format!(\"{var}.{version}\")\n                }\n                None => var.to_string(),\n            };\n            for access in access {\n                if let Access::ArrayAccess(index) = access {\n                    visit_expression(index, env);\n                }\n            }\n            visit_expression(rhe, env);\n        }\n        MultiSubstitution { lhe, rhe, .. } => {\n            visit_expression(lhe, env);\n            visit_expression(rhe, env);\n        }\n        LogCall { args, .. } => {\n            use LogArgument::*;\n            for arg in args {\n                if let LogExp(value) = arg {\n                    visit_expression(value, env);\n                }\n            }\n        }\n        Return { value, .. } => {\n            visit_expression(value, env);\n        }\n        ConstraintEquality { lhe, rhe, .. } => {\n            visit_expression(lhe, env);\n            visit_expression(rhe, env);\n        }\n        Assert { arg, .. } => {\n            visit_expression(arg, env);\n        }\n        InitializationBlock { initializations, .. } => {\n            for init in initializations {\n                visit_statement(init, env, reports);\n            }\n        }\n        While { cond, stmt, .. } => {\n            visit_expression(cond, env);\n            visit_statement(stmt, env, reports);\n        }\n        Block { stmts, .. } => {\n            env.add_variable_block();\n            for stmt in stmts {\n                visit_statement(stmt, env, reports);\n            }\n            env.remove_variable_block();\n        }\n        IfThenElse { cond, if_case, else_case, .. } => {\n            visit_expression(cond, env);\n            visit_statement(if_case, env, reports);\n            if let Some(else_case) = else_case {\n                visit_statement(else_case, env, reports);\n            }\n        }\n    }\n}\n\nfn visit_expression(expr: &mut Expression, env: &DeclarationEnvironment) {\n    use Access::*;\n    use Expression::*;\n    match expr {\n        Variable { name, access, .. } => {\n            trace!(\"visiting variable '{name}'\");\n            *name = match env.get_current_version(name) {\n                Some(version) => {\n                    trace!(\"renaming occurrence of variable `{name}` to `{name}.{version}`\");\n                    format!(\"{name}.{version}\")\n                }\n                None => name.clone(),\n            };\n            for access in access {\n                if let ArrayAccess(index) = access {\n                    visit_expression(index, env);\n                }\n            }\n        }\n        InfixOp { lhe, rhe, .. } => {\n            visit_expression(lhe, env);\n            visit_expression(rhe, env);\n        }\n        PrefixOp { rhe, .. } => {\n            visit_expression(rhe, env);\n        }\n        InlineSwitchOp { cond, if_true, if_false, .. } => {\n            visit_expression(cond, env);\n            visit_expression(if_true, env);\n            visit_expression(if_false, env);\n        }\n        Number(_, _) => {}\n        Call { args, .. } => {\n            for arg in args {\n                visit_expression(arg, env);\n            }\n        }\n        Tuple { values, .. } | ArrayInLine { values, .. } => {\n            for value in values {\n                visit_expression(value, env);\n            }\n        }\n        ParallelOp { rhe, .. } => {\n            visit_expression(rhe, env);\n        }\n        AnonymousComponent { params, signals, names, .. } => {\n            for param in params {\n                visit_expression(param, env)\n            }\n            for signal in signals {\n                visit_expression(signal, env)\n            }\n            if let Some(names) = names {\n                for (_, name) in names {\n                    trace!(\"visiting variable '{name}'\");\n                    *name = match env.get_current_version(name) {\n                        Some(version) => {\n                            trace!(\n                                \"renaming occurrence of variable `{name}` to `{name}.{version}`\"\n                            );\n                            format!(\"{name}.{version}\")\n                        }\n                        None => name.clone(),\n                    };\n                }\n            }\n        }\n    }\n}\n\nfn build_report(name: &str, primary_meta: &Meta, secondary_decl: &Declaration) -> Report {\n    CFGError::ShadowingVariableWarning {\n        name: name.to_string(),\n        primary_file_id: primary_meta.file_id,\n        primary_location: primary_meta.file_location(),\n        secondary_file_id: secondary_decl.file_id(),\n        secondary_location: secondary_decl.file_location(),\n    }\n    .into()\n}\n"
  },
  {
    "path": "program_structure/src/intermediate_representation/declarations.rs",
    "content": "use std::collections::HashMap;\n\nuse crate::file_definition::{FileID, FileLocation};\nuse crate::ir::*;\n\n/// A structure used to track declared variables.\n#[derive(Default, Clone)]\npub struct Declarations(HashMap<VariableName, Declaration>);\n\nimpl Declarations {\n    #[must_use]\n    pub fn new() -> Declarations {\n        Declarations::default()\n    }\n\n    pub fn add_declaration(&mut self, declaration: &Declaration) {\n        assert!(\n            self.0.insert(declaration.variable_name().clone(), declaration.clone()).is_none(),\n            \"variable `{}` already tracked by declaration map\",\n            declaration.variable_name()\n        );\n    }\n\n    #[must_use]\n    pub fn get_declaration(&self, name: &VariableName) -> Option<&Declaration> {\n        self.0.get(&name.without_version())\n    }\n\n    #[must_use]\n    pub fn get_type(&self, name: &VariableName) -> Option<&VariableType> {\n        self.get_declaration(name).map(|decl| decl.variable_type())\n    }\n\n    pub fn iter(&self) -> impl Iterator<Item = (&VariableName, &Declaration)> {\n        self.0.iter()\n    }\n\n    #[must_use]\n    pub fn len(&self) -> usize {\n        self.0.len()\n    }\n\n    #[must_use]\n    pub fn is_empty(&self) -> bool {\n        self.0.is_empty()\n    }\n}\n\n/// To avoid having to add a new declaration for each new version of a variable\n/// we track all declarations as part of the CFG header.\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct Declaration {\n    name: VariableName,\n    var_type: VariableType,\n    dimensions: Vec<Expression>,\n    file_id: Option<FileID>,\n    file_location: FileLocation,\n}\n\nimpl Declaration {\n    pub fn new(\n        name: &VariableName,\n        var_type: &VariableType,\n        dimensions: &[Expression],\n        file_id: &Option<FileID>,\n        file_location: &FileLocation,\n    ) -> Declaration {\n        Declaration {\n            name: name.clone(),\n            var_type: var_type.clone(),\n            dimensions: dimensions.to_vec(),\n            file_id: *file_id,\n            file_location: file_location.clone(),\n        }\n    }\n\n    #[must_use]\n    pub fn file_id(&self) -> Option<FileID> {\n        self.file_id\n    }\n\n    #[must_use]\n    pub fn file_location(&self) -> FileLocation {\n        self.file_location.clone()\n    }\n\n    #[must_use]\n    pub fn variable_name(&self) -> &VariableName {\n        &self.name\n    }\n\n    #[must_use]\n    pub fn variable_type(&self) -> &VariableType {\n        &self.var_type\n    }\n\n    #[must_use]\n    pub fn dimensions(&self) -> &Vec<Expression> {\n        &self.dimensions\n    }\n}\n"
  },
  {
    "path": "program_structure/src/intermediate_representation/degree_meta.rs",
    "content": "use log::trace;\nuse std::cmp::{Ordering, min, max};\nuse std::collections::HashMap;\nuse std::fmt;\n\nuse super::{VariableName, VariableType};\n\n/// The degree of an expression.\n#[derive(Clone, Copy, PartialEq, Eq, Hash)]\npub enum Degree {\n    Constant,\n    Linear,\n    Quadratic,\n    NonQuadratic,\n}\n\n// Degrees are linearly ordered.\nimpl PartialOrd<Degree> for Degree {\n    fn partial_cmp(&self, other: &Degree) -> Option<Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\n// Degrees are linearly ordered.\nimpl Ord for Degree {\n    fn cmp(&self, other: &Degree) -> Ordering {\n        use Degree::*;\n        match (self, other) {\n            // `Constant <= _`\n            (Constant, Constant) => Ordering::Equal,\n            (Constant, Linear) | (Constant, Quadratic) | (Constant, NonQuadratic) => Ordering::Less,\n            // `Linear <= _`\n            (Linear, Linear) => Ordering::Equal,\n            (Linear, Quadratic) | (Linear, NonQuadratic) => Ordering::Less,\n            // `Quadratic <= _`\n            (Quadratic, Quadratic) => Ordering::Equal,\n            (Quadratic, NonQuadratic) => Ordering::Less,\n            // `NonQuadratic <= _`\n            (NonQuadratic, NonQuadratic) => Ordering::Equal,\n            // All other cases are on the form `_ >= _`.\n            _ => Ordering::Greater,\n        }\n    }\n}\n\nimpl Degree {\n    pub fn add(&self, other: &Degree) -> Degree {\n        max(*self, *other)\n    }\n\n    pub fn infix_sub(&self, other: &Degree) -> Degree {\n        max(*self, *other)\n    }\n\n    pub fn mul(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        match (self, other) {\n            (Constant, _) => *other,\n            (_, Constant) => *self,\n            (Linear, Linear) => Quadratic,\n            _ => NonQuadratic,\n        }\n    }\n\n    pub fn pow(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if (*self, *other) == (Constant, Constant) {\n            Constant\n        } else {\n            NonQuadratic\n        }\n    }\n\n    pub fn div(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if *other == Constant {\n            *self\n        } else {\n            NonQuadratic\n        }\n    }\n\n    pub fn int_div(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if (*self, *other) == (Constant, Constant) {\n            Constant\n        } else {\n            NonQuadratic\n        }\n    }\n\n    pub fn modulo(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if (*self, *other) == (Constant, Constant) {\n            Constant\n        } else {\n            NonQuadratic\n        }\n    }\n\n    pub fn shift_left(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if (*self, *other) == (Constant, Constant) {\n            Constant\n        } else {\n            NonQuadratic\n        }\n    }\n\n    pub fn shift_right(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if (*self, *other) == (Constant, Constant) {\n            Constant\n        } else {\n            NonQuadratic\n        }\n    }\n\n    pub fn lesser(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if (*self, *other) == (Constant, Constant) {\n            Constant\n        } else {\n            NonQuadratic\n        }\n    }\n\n    pub fn greater(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if (*self, *other) == (Constant, Constant) {\n            Constant\n        } else {\n            NonQuadratic\n        }\n    }\n\n    pub fn lesser_eq(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if (*self, *other) == (Constant, Constant) {\n            Constant\n        } else {\n            NonQuadratic\n        }\n    }\n\n    pub fn greater_eq(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if (*self, *other) == (Constant, Constant) {\n            Constant\n        } else {\n            NonQuadratic\n        }\n    }\n    pub fn equal(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if (*self, *other) == (Constant, Constant) {\n            Constant\n        } else {\n            NonQuadratic\n        }\n    }\n\n    pub fn not_equal(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if (*self, *other) == (Constant, Constant) {\n            Constant\n        } else {\n            NonQuadratic\n        }\n    }\n\n    pub fn bit_or(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if (*self, *other) == (Constant, Constant) {\n            Constant\n        } else {\n            NonQuadratic\n        }\n    }\n\n    pub fn bit_and(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if (*self, *other) == (Constant, Constant) {\n            Constant\n        } else {\n            NonQuadratic\n        }\n    }\n\n    pub fn bit_xor(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if (*self, *other) == (Constant, Constant) {\n            Constant\n        } else {\n            NonQuadratic\n        }\n    }\n\n    pub fn bool_or(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if (*self, *other) == (Constant, Constant) {\n            Constant\n        } else {\n            NonQuadratic\n        }\n    }\n\n    pub fn bool_and(&self, other: &Degree) -> Degree {\n        use Degree::*;\n        if (*self, *other) == (Constant, Constant) {\n            Constant\n        } else {\n            NonQuadratic\n        }\n    }\n\n    pub fn prefix_sub(&self) -> Degree {\n        *self\n    }\n\n    pub fn complement(&self) -> Degree {\n        use Degree::*;\n        Quadratic\n    }\n\n    pub fn bool_not(&self) -> Degree {\n        use Degree::*;\n        Quadratic\n    }\n}\n\nimpl fmt::Debug for Degree {\n    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {\n        use Degree::*;\n        match self {\n            Constant => write!(f, \"constant\"),\n            Linear => write!(f, \"linear\"),\n            Quadratic => write!(f, \"quadratic\"),\n            NonQuadratic => write!(f, \"non-quadratic\"),\n        }\n    }\n}\n\n/// An inclusive range of degrees.\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct DegreeRange(Degree, Degree);\n\nimpl DegreeRange {\n    #[must_use]\n    pub fn new(start: Degree, end: Degree) -> DegreeRange {\n        DegreeRange(start, end)\n    }\n\n    #[must_use]\n    pub fn start(&self) -> Degree {\n        self.0\n    }\n\n    #[must_use]\n    pub fn end(&self) -> Degree {\n        self.1\n    }\n\n    #[must_use]\n    pub fn contains(&self, degree: Degree) -> bool {\n        self.start() <= degree && degree <= self.end()\n    }\n\n    /// Returns true if the upper bound is at most constant.\n    #[must_use]\n    pub fn is_constant(&self) -> bool {\n        self.end() <= Degree::Constant\n    }\n\n    /// Returns true if the upper bound is at most linear.\n    #[must_use]\n    pub fn is_linear(&self) -> bool {\n        self.end() <= Degree::Linear\n    }\n\n    /// Returns true if the upper bound is at most quadratic.\n    #[must_use]\n    pub fn is_quadratic(&self) -> bool {\n        self.end() <= Degree::Quadratic\n    }\n\n    /// Computes the infimum (under inverse inclusion) of `self` and `other`.\n    /// Note, if the two ranges overlap this will simply be the union of `self`\n    /// and `other`.\n    pub fn inf(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange(min(self.start(), other.start()), max(self.end(), other.end()))\n    }\n\n    /// Constructs the infimum (under inverse inclusion) of the given degree ranges.\n    /// Note, if the ranges overlap this will simply be the union of all the ranges.\n    ///\n    /// # Panics\n    ///\n    /// This method will panic if the iterator is empty.\n    pub fn iter_inf<'a, T: IntoIterator<Item = &'a DegreeRange>>(ranges: T) -> DegreeRange {\n        let mut ranges = ranges.into_iter();\n        if let Some(range) = ranges.next() {\n            let mut result = range.clone();\n            for range in ranges {\n                result = result.inf(range);\n            }\n            result\n        } else {\n            panic!(\"iterator must not be empty\")\n        }\n    }\n\n    // If the iterator is not empty and all the ranges are `Some(range)` this\n    // method will return the same as `DegreeRange::iter_inf`, otherwise it will\n    // return `None`.\n    pub fn iter_opt<'a, T: IntoIterator<Item = Option<&'a DegreeRange>>>(\n        ranges: T,\n    ) -> Option<DegreeRange> {\n        let ranges = ranges.into_iter().collect::<Option<Vec<_>>>();\n        match ranges {\n            Some(ranges) if !ranges.is_empty() => Some(Self::iter_inf(ranges)),\n            _ => None,\n        }\n    }\n\n    pub fn add(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().add(&other.start()), self.end().add(&other.end()))\n    }\n\n    pub fn infix_sub(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().infix_sub(&other.start()), self.end().infix_sub(&other.end()))\n    }\n\n    pub fn mul(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().mul(&other.start()), self.end().mul(&other.end()))\n    }\n\n    pub fn pow(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().pow(&other.start()), self.end().pow(&other.end()))\n    }\n\n    pub fn div(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().div(&other.start()), self.end().div(&other.end()))\n    }\n\n    pub fn int_div(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().int_div(&other.start()), self.end().int_div(&other.end()))\n    }\n\n    pub fn modulo(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().modulo(&other.start()), self.end().modulo(&other.end()))\n    }\n\n    pub fn shift_left(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(\n            self.start().shift_left(&other.start()),\n            self.end().shift_left(&other.end()),\n        )\n    }\n\n    pub fn shift_right(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(\n            self.start().shift_right(&other.start()),\n            self.end().shift_right(&other.end()),\n        )\n    }\n\n    pub fn lesser(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().lesser(&other.start()), self.end().lesser(&other.end()))\n    }\n\n    pub fn greater(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().greater(&other.start()), self.end().greater(&other.end()))\n    }\n\n    pub fn lesser_eq(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().lesser_eq(&other.start()), self.end().lesser_eq(&other.end()))\n    }\n\n    pub fn greater_eq(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(\n            self.start().greater_eq(&other.start()),\n            self.end().greater_eq(&other.end()),\n        )\n    }\n    pub fn equal(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().equal(&other.start()), self.end().equal(&other.end()))\n    }\n\n    pub fn not_equal(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().not_equal(&other.start()), self.end().not_equal(&other.end()))\n    }\n\n    pub fn bit_or(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().bit_or(&other.start()), self.end().bit_or(&other.end()))\n    }\n\n    pub fn bit_and(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().bit_and(&other.start()), self.end().bit_and(&other.end()))\n    }\n\n    pub fn bit_xor(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().bit_xor(&other.start()), self.end().bit_xor(&other.end()))\n    }\n\n    pub fn bool_or(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().bool_or(&other.start()), self.end().bool_or(&other.end()))\n    }\n\n    pub fn bool_and(&self, other: &DegreeRange) -> DegreeRange {\n        DegreeRange::new(self.start().bool_and(&other.start()), self.end().bool_and(&other.end()))\n    }\n\n    pub fn prefix_sub(&self) -> DegreeRange {\n        DegreeRange::new(self.start().prefix_sub(), self.end().prefix_sub())\n    }\n\n    pub fn complement(&self) -> DegreeRange {\n        DegreeRange::new(self.start().complement(), self.end().complement())\n    }\n\n    pub fn bool_not(&self) -> DegreeRange {\n        DegreeRange::new(self.start().bool_not(), self.end().bool_not())\n    }\n}\n\n// Construct a range containing a single element.\nimpl From<Degree> for DegreeRange {\n    fn from(degree: Degree) -> DegreeRange {\n        DegreeRange(degree, degree)\n    }\n}\n\nimpl fmt::Debug for DegreeRange {\n    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {\n        write!(f, \"[{:?}, {:?}]\", self.start(), self.end())\n    }\n}\n\n/// This type is used to track degrees of individual variables during degree\n/// propagation.\n#[derive(Default, Clone)]\npub struct DegreeEnvironment {\n    // Even though we assume SSA a single variable may have different degrees\n    // because of parameter-dependent control flow. We track the lower and upper\n    // bounds of the degree of each variable.\n    degree_ranges: HashMap<VariableName, DegreeRange>,\n    var_types: HashMap<VariableName, VariableType>,\n}\n\nimpl DegreeEnvironment {\n    pub fn new() -> DegreeEnvironment {\n        DegreeEnvironment::default()\n    }\n\n    /// Sets the degree range of the given variable. Returns true on first update.\n    /// TODO: Should probably take the supremum of the given range and any\n    /// existing range.\n    pub fn set_degree(&mut self, var: &VariableName, range: &DegreeRange) -> bool {\n        if self.degree_ranges.insert(var.clone(), range.clone()).is_none() {\n            trace!(\"setting degree range of `{var:?}` to {range:?}\");\n            true\n        } else {\n            false\n        }\n    }\n\n    /// Sets the type of the given variable.\n    pub fn set_type(&mut self, var: &VariableName, var_type: &VariableType) {\n        if self.var_types.insert(var.clone(), var_type.clone()).is_none() {\n            trace!(\"setting type of `{var:?}` to `{var_type}`\");\n        }\n    }\n\n    /// Gets the degree range of the given variable.\n    #[must_use]\n    pub fn degree(&self, var: &VariableName) -> Option<&DegreeRange> {\n        self.degree_ranges.get(var)\n    }\n\n    /// Returns true if the given variable is a local variable.\n    #[must_use]\n    pub fn is_local(&self, var: &VariableName) -> bool {\n        matches!(self.var_types.get(var), Some(VariableType::Local))\n    }\n}\n\npub trait DegreeMeta {\n    /// Compute expression degrees for this node and child nodes. Returns true\n    /// if the node (or a child node) is updated.\n    fn propagate_degrees(&mut self, env: &DegreeEnvironment) -> bool;\n\n    /// Returns an inclusive range the degree of the node may take.\n    #[must_use]\n    fn degree(&self) -> Option<&DegreeRange>;\n}\n\n#[derive(Default, Clone)]\npub struct DegreeKnowledge {\n    // The inclusive range the degree of the node may take.\n    degree_range: Option<DegreeRange>,\n}\n\nimpl DegreeKnowledge {\n    #[must_use]\n    pub fn new() -> DegreeKnowledge {\n        DegreeKnowledge::default()\n    }\n\n    pub fn set_degree(&mut self, range: &DegreeRange) -> bool {\n        let result = self.degree_range.is_none();\n        self.degree_range = Some(range.clone());\n        result\n    }\n\n    #[must_use]\n    pub fn degree(&self) -> Option<&DegreeRange> {\n        self.degree_range.as_ref()\n    }\n\n    /// Returns true if the degree range is known, and the upper bound is\n    /// at most constant.\n    #[must_use]\n    pub fn is_constant(&self) -> bool {\n        if let Some(range) = &self.degree_range {\n            range.is_constant()\n        } else {\n            false\n        }\n    }\n\n    /// Returns true if the degree range is known, and the upper bound is\n    /// at most linear.\n    #[must_use]\n    pub fn is_linear(&self) -> bool {\n        if let Some(range) = &self.degree_range {\n            range.is_linear()\n        } else {\n            false\n        }\n    }\n\n    /// Returns true if the degree range is known, and the upper bound is\n    /// at most quadratic.\n    #[must_use]\n    pub fn is_quadratic(&self) -> bool {\n        if let Some(range) = &self.degree_range {\n            range.is_quadratic()\n        } else {\n            false\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::{Degree, DegreeKnowledge};\n\n    #[test]\n    fn test_value_knowledge() {\n        let mut value = DegreeKnowledge::new();\n        assert!(value.degree().is_none());\n        assert!(!value.is_constant());\n        assert!(!value.is_linear());\n        assert!(!value.is_quadratic());\n\n        assert!(value.set_degree(&Degree::Constant.into()));\n        assert!(value.degree().is_some());\n        assert!(value.is_constant());\n        assert!(value.is_linear());\n        assert!(value.is_quadratic());\n\n        assert!(!value.set_degree(&Degree::Linear.into()));\n        assert!(value.degree().is_some());\n        assert!(!value.is_constant());\n        assert!(value.is_linear());\n        assert!(value.is_quadratic());\n\n        assert!(!value.set_degree(&Degree::Quadratic.into()));\n        assert!(value.degree().is_some());\n        assert!(!value.is_constant());\n        assert!(!value.is_linear());\n        assert!(value.is_quadratic());\n\n        assert!(!value.set_degree(&Degree::NonQuadratic.into()));\n        assert!(value.degree().is_some());\n        assert!(!value.is_constant());\n        assert!(!value.is_linear());\n        assert!(!value.is_quadratic());\n    }\n}\n"
  },
  {
    "path": "program_structure/src/intermediate_representation/errors.rs",
    "content": "use thiserror::Error;\n\nuse crate::report_code::ReportCode;\nuse crate::report::Report;\nuse crate::file_definition::{FileID, FileLocation};\n\n/// Error enum for IR generation errors.\n#[derive(Debug, Error)]\npub enum IRError {\n    #[error(\"The variable `{name}` is read before it is declared/written.\")]\n    UndefinedVariableError { name: String, file_id: Option<FileID>, file_location: FileLocation },\n    #[error(\"The variable name `{name}` contains invalid characters.\")]\n    InvalidVariableNameError { name: String, file_id: Option<FileID>, file_location: FileLocation },\n}\n\npub type IRResult<T> = Result<T, IRError>;\n\nimpl IRError {\n    pub fn produce_report(error: Self) -> Report {\n        use IRError::*;\n        match error {\n            UndefinedVariableError { name, file_id, file_location } => {\n                let mut report = Report::error(\n                    format!(\"The variable '{name}' is used before it is defined.\"),\n                    ReportCode::UninitializedSymbolInExpression,\n                );\n                if let Some(file_id) = file_id {\n                    report.add_primary(\n                        file_location,\n                        file_id,\n                        format!(\"The variable `{name}` is first seen here.\"),\n                    );\n                }\n                report\n            }\n            InvalidVariableNameError { name, file_id, file_location } => {\n                let mut report = Report::error(\n                    format!(\"Invalid variable name `{name}`.\"),\n                    ReportCode::ParseFail,\n                );\n                if let Some(file_id) = file_id {\n                    report.add_primary(\n                        file_location,\n                        file_id,\n                        \"This variable name contains invalid characters.\".to_string(),\n                    );\n                }\n                report\n            }\n        }\n    }\n}\n\nimpl From<IRError> for Report {\n    fn from(error: IRError) -> Report {\n        IRError::produce_report(error)\n    }\n}\n"
  },
  {
    "path": "program_structure/src/intermediate_representation/expression_impl.rs",
    "content": "use log::trace;\nuse num_traits::Zero;\nuse std::collections::HashSet;\nuse std::fmt;\nuse std::hash::{Hash, Hasher};\n\nuse circom_algebra::modular_arithmetic;\n\nuse super::declarations::Declarations;\nuse super::degree_meta::{Degree, DegreeEnvironment, DegreeMeta, DegreeRange};\nuse super::ir::*;\nuse super::type_meta::TypeMeta;\nuse super::value_meta::{ValueEnvironment, ValueMeta, ValueReduction};\nuse super::variable_meta::{VariableMeta, VariableUse, VariableUses};\n\nimpl Expression {\n    #[must_use]\n    pub fn meta(&self) -> &Meta {\n        use Expression::*;\n        match self {\n            InfixOp { meta, .. }\n            | PrefixOp { meta, .. }\n            | SwitchOp { meta, .. }\n            | Variable { meta, .. }\n            | Number(meta, ..)\n            | Call { meta, .. }\n            | InlineArray { meta, .. }\n            | Update { meta, .. }\n            | Access { meta, .. }\n            | Phi { meta, .. } => meta,\n        }\n    }\n\n    #[must_use]\n    pub fn meta_mut(&mut self) -> &mut Meta {\n        use Expression::*;\n        match self {\n            InfixOp { meta, .. }\n            | PrefixOp { meta, .. }\n            | SwitchOp { meta, .. }\n            | Variable { meta, .. }\n            | Number(meta, ..)\n            | Call { meta, .. }\n            | InlineArray { meta, .. }\n            | Update { meta, .. }\n            | Access { meta, .. }\n            | Phi { meta, .. } => meta,\n        }\n    }\n}\n\n/// Syntactic equality for expressions.\nimpl PartialEq for Expression {\n    fn eq(&self, other: &Expression) -> bool {\n        use Expression::*;\n        match (self, other) {\n            (\n                InfixOp { lhe: self_lhe, infix_op: self_op, rhe: self_rhe, .. },\n                InfixOp { lhe: other_lhe, infix_op: other_op, rhe: other_rhe, .. },\n            ) => self_op == other_op && self_lhe == other_lhe && self_rhe == other_rhe,\n            (\n                PrefixOp { prefix_op: self_op, rhe: self_rhe, .. },\n                PrefixOp { prefix_op: other_op, rhe: other_rhe, .. },\n            ) => self_op == other_op && self_rhe == other_rhe,\n            (\n                SwitchOp { cond: self_cond, if_true: self_true, if_false: self_false, .. },\n                SwitchOp { cond: other_cond, if_true: other_true, if_false: other_false, .. },\n            ) => self_cond == other_cond && self_true == other_true && self_false == other_false,\n            (Variable { name: self_name, .. }, Variable { name: other_name, .. }) => {\n                self_name == other_name\n            }\n            (Number(_, self_value), Number(_, other_value)) => self_value == other_value,\n            (\n                Call { name: self_id, args: self_args, .. },\n                Call { name: other_id, args: other_args, .. },\n            ) => self_id == other_id && self_args == other_args,\n            (InlineArray { values: self_values, .. }, InlineArray { values: other_values, .. }) => {\n                self_values == other_values\n            }\n            (\n                Update { var: self_var, access: self_access, rhe: self_rhe, .. },\n                Update { var: other_var, access: other_access, rhe: other_rhe, .. },\n            ) => self_var == other_var && self_access == other_access && self_rhe == other_rhe,\n            (\n                Access { var: self_var, access: self_access, .. },\n                Access { var: other_var, access: other_access, .. },\n            ) => self_var == other_var && self_access == other_access,\n            (Phi { args: self_args, .. }, Phi { args: other_args, .. }) => self_args == other_args,\n            _ => false,\n        }\n    }\n}\n\nimpl Eq for Expression {}\n\nimpl Hash for Expression {\n    fn hash<H: Hasher>(&self, state: &mut H) {\n        use Expression::*;\n        match self {\n            InfixOp { lhe, rhe, .. } => {\n                lhe.hash(state);\n                rhe.hash(state);\n            }\n            PrefixOp { rhe, .. } => {\n                rhe.hash(state);\n            }\n            SwitchOp { cond, if_true, if_false, .. } => {\n                cond.hash(state);\n                if_true.hash(state);\n                if_false.hash(state);\n            }\n            Variable { name, .. } => {\n                name.hash(state);\n            }\n            Call { args, .. } => {\n                args.hash(state);\n            }\n            InlineArray { values, .. } => {\n                values.hash(state);\n            }\n            Access { var, access, .. } => {\n                var.hash(state);\n                access.hash(state);\n            }\n            Update { var, access, rhe, .. } => {\n                var.hash(state);\n                access.hash(state);\n                rhe.hash(state);\n            }\n            Phi { args, .. } => {\n                args.hash(state);\n            }\n            Number(_, value) => {\n                value.hash(state);\n            }\n        }\n    }\n}\n\nimpl DegreeMeta for Expression {\n    fn propagate_degrees(&mut self, env: &DegreeEnvironment) -> bool {\n        let mut result = false;\n\n        use Degree::*;\n        use Expression::*;\n        match self {\n            InfixOp { meta, lhe, rhe, infix_op } => {\n                result = result || lhe.propagate_degrees(env);\n                result = result || rhe.propagate_degrees(env);\n                let range = infix_op.propagate_degrees(lhe.degree(), rhe.degree());\n                if let Some(range) = range {\n                    result = result || meta.degree_knowledge_mut().set_degree(&range);\n                }\n                result\n            }\n            PrefixOp { meta, rhe, prefix_op, .. } => {\n                result = result || rhe.propagate_degrees(env);\n                let range = prefix_op.propagate_degrees(rhe.degree());\n                if let Some(range) = range {\n                    result = result || meta.degree_knowledge_mut().set_degree(&range);\n                }\n                result\n            }\n            SwitchOp { meta, cond, if_true, if_false, .. } => {\n                // If the condition has constant degree, the expression can be\n                // desugared using an if-statement and the maximum degree in\n                // each case will be the maximum of the individual if- and\n                // else-case degrees.\n                result = result || cond.propagate_degrees(env);\n                result = result || if_true.propagate_degrees(env);\n                result = result || if_false.propagate_degrees(env);\n                let Some(range) = cond.degree() else {\n                    return result;\n                };\n                if range.is_constant() {\n                    // The condition has constant degree.\n                    if let Some(range) =\n                        DegreeRange::iter_opt([if_true.degree(), if_false.degree()])\n                    {\n                        result = result || meta.degree_knowledge_mut().set_degree(&range);\n                    }\n                }\n                result\n            }\n            Variable { meta, name } => {\n                if let Some(range) = env.degree(name) {\n                    result = result || meta.degree_knowledge_mut().set_degree(range);\n                }\n                result\n            }\n            Call { meta, args, .. } => {\n                for arg in args.iter_mut() {\n                    result = result || arg.propagate_degrees(env);\n                }\n                // If one or more non-constant arguments is passed to the function we cannot\n                // say anything about the degree of the output. If the function only takes\n                // constant arguments the output must also be constant.\n                if args.iter().all(|arg| {\n                    if let Some(range) = arg.degree() {\n                        range.is_constant()\n                    } else {\n                        false\n                    }\n                }) {\n                    result = result || meta.degree_knowledge_mut().set_degree(&Constant.into())\n                }\n                result\n            }\n            InlineArray { meta, values } => {\n                // The degree range of an array is the infimum of the ranges of all elements.\n                for value in values.iter_mut() {\n                    result = result || value.propagate_degrees(env);\n                }\n                let range = DegreeRange::iter_opt(values.iter().map(|value| value.degree()));\n                if let Some(range) = range {\n                    result = result || meta.degree_knowledge_mut().set_degree(&range);\n                }\n                result\n            }\n            Access { meta, var, access } => {\n                // Accesses are ignored when determining the degree of a variable.\n                for access in access.iter_mut() {\n                    if let AccessType::ArrayAccess(index) = access {\n                        result = result || index.propagate_degrees(env);\n                    }\n                }\n                if let Some(range) = env.degree(var) {\n                    result = result || meta.degree_knowledge_mut().set_degree(range);\n                }\n                result\n            }\n            Update { meta, var, access, rhe, .. } => {\n                // Accesses are ignored when determining the degree of a variable.\n                result = result || rhe.propagate_degrees(env);\n                for access in access.iter_mut() {\n                    if let AccessType::ArrayAccess(index) = access {\n                        result = result || index.propagate_degrees(env);\n                    }\n                }\n                if env.degree(var).is_none() {\n                    // This is the first assignment to the array. The degree is given by the RHS.\n                    if let Some(range) = rhe.degree() {\n                        result = result || meta.degree_knowledge_mut().set_degree(range);\n                    }\n                } else {\n                    // The array has been assigned to previously. The degree is the infimum of\n                    // the degrees of `var` and the RHS.\n                    let range = DegreeRange::iter_opt([env.degree(var), rhe.degree()]);\n                    if let Some(range) = range {\n                        result = result || meta.degree_knowledge_mut().set_degree(&range);\n                    }\n                }\n                result\n            }\n            Phi { meta, args } => {\n                // The degree range of a phi expression is the infimum of the ranges of all the arguments.\n                let range = DegreeRange::iter_opt(args.iter().map(|arg| env.degree(arg)));\n                if let Some(range) = range {\n                    result = result || meta.degree_knowledge_mut().set_degree(&range);\n                }\n                result\n            }\n            Number(meta, _) => {\n                // Constants have constant degree.\n                meta.degree_knowledge_mut().set_degree(&Constant.into())\n            }\n        }\n    }\n\n    fn degree(&self) -> Option<&DegreeRange> {\n        self.meta().degree_knowledge().degree()\n    }\n}\n\nimpl TypeMeta for Expression {\n    fn propagate_types(&mut self, vars: &Declarations) {\n        use Expression::*;\n        match self {\n            InfixOp { lhe, rhe, .. } => {\n                lhe.propagate_types(vars);\n                rhe.propagate_types(vars);\n            }\n            PrefixOp { rhe, .. } => {\n                rhe.propagate_types(vars);\n            }\n            SwitchOp { cond, if_true, if_false, .. } => {\n                cond.propagate_types(vars);\n                if_true.propagate_types(vars);\n                if_false.propagate_types(vars);\n            }\n            Variable { meta, name } => {\n                if let Some(var_type) = vars.get_type(name) {\n                    meta.type_knowledge_mut().set_variable_type(var_type);\n                }\n            }\n            Call { args, .. } => {\n                for arg in args {\n                    arg.propagate_types(vars);\n                }\n            }\n            InlineArray { values, .. } => {\n                for value in values {\n                    value.propagate_types(vars);\n                }\n            }\n            Access { meta, var, access } => {\n                for access in access.iter_mut() {\n                    if let AccessType::ArrayAccess(index) = access {\n                        index.propagate_types(vars);\n                    }\n                }\n                if let Some(var_type) = vars.get_type(var) {\n                    meta.type_knowledge_mut().set_variable_type(var_type);\n                }\n            }\n            Update { meta, var, access, rhe, .. } => {\n                rhe.propagate_types(vars);\n                for access in access.iter_mut() {\n                    if let AccessType::ArrayAccess(index) = access {\n                        index.propagate_types(vars);\n                    }\n                }\n                if let Some(var_type) = vars.get_type(var) {\n                    meta.type_knowledge_mut().set_variable_type(var_type);\n                }\n            }\n            Phi { .. } => {\n                // All phi node arguments are local variables.\n            }\n            Number(_, _) => {}\n        }\n    }\n\n    fn is_local(&self) -> bool {\n        self.meta().type_knowledge().is_local()\n    }\n\n    fn is_signal(&self) -> bool {\n        self.meta().type_knowledge().is_signal()\n    }\n\n    fn is_component(&self) -> bool {\n        self.meta().type_knowledge().is_component()\n    }\n\n    fn variable_type(&self) -> Option<&VariableType> {\n        self.meta().type_knowledge().variable_type()\n    }\n}\n\nimpl VariableMeta for Expression {\n    fn cache_variable_use(&mut self) {\n        let mut locals_read = VariableUses::new();\n        let mut signals_read = VariableUses::new();\n        let mut components_read = VariableUses::new();\n\n        use Expression::*;\n        match self {\n            InfixOp { lhe, rhe, .. } => {\n                lhe.cache_variable_use();\n                rhe.cache_variable_use();\n                locals_read.extend(lhe.locals_read().iter().cloned());\n                locals_read.extend(rhe.locals_read().iter().cloned());\n                signals_read.extend(lhe.signals_read().iter().cloned());\n                signals_read.extend(rhe.signals_read().iter().cloned());\n                components_read.extend(lhe.components_read().iter().cloned());\n                components_read.extend(rhe.components_read().iter().cloned());\n            }\n            PrefixOp { rhe, .. } => {\n                rhe.cache_variable_use();\n                locals_read.extend(rhe.locals_read().iter().cloned());\n                signals_read.extend(rhe.signals_read().iter().cloned());\n                components_read.extend(rhe.components_read().iter().cloned());\n            }\n            SwitchOp { cond, if_true, if_false, .. } => {\n                cond.cache_variable_use();\n                if_true.cache_variable_use();\n                if_false.cache_variable_use();\n                locals_read.extend(cond.locals_read().clone());\n                locals_read.extend(if_true.locals_read().clone());\n                locals_read.extend(if_false.locals_read().clone());\n                signals_read.extend(cond.signals_read().clone());\n                signals_read.extend(if_true.signals_read().clone());\n                signals_read.extend(if_false.signals_read().clone());\n                components_read.extend(cond.components_read().clone());\n                components_read.extend(if_true.components_read().clone());\n                components_read.extend(if_false.components_read().clone());\n            }\n            Variable { meta, name } => {\n                match meta.type_knowledge().variable_type() {\n                    Some(VariableType::Local) => {\n                        trace!(\"adding `{name:?}` to local variables read\");\n                        locals_read.insert(VariableUse::new(meta, name, &Vec::new()));\n                    }\n                    Some(VariableType::Component | VariableType::AnonymousComponent) => {\n                        trace!(\"adding `{name:?}` to components read\");\n                        components_read.insert(VariableUse::new(meta, name, &Vec::new()));\n                    }\n                    Some(VariableType::Signal(_, _)) => {\n                        trace!(\"adding `{name:?}` to signals read\");\n                        signals_read.insert(VariableUse::new(meta, name, &Vec::new()));\n                    }\n                    None => {\n                        // If the variable type is unknown we ignore it.\n                        trace!(\"variable `{name:?}` of unknown type read\");\n                    }\n                }\n            }\n            Call { args, .. } => {\n                for arg in args {\n                    arg.cache_variable_use();\n                    locals_read.extend(arg.locals_read().clone());\n                    signals_read.extend(arg.signals_read().clone());\n                    components_read.extend(arg.components_read().clone());\n                }\n            }\n            Phi { meta, args } => {\n                locals_read\n                    .extend(args.iter().map(|name| VariableUse::new(meta, name, &Vec::new())));\n            }\n            InlineArray { values, .. } => {\n                for value in values {\n                    value.cache_variable_use();\n                    locals_read.extend(value.locals_read().clone());\n                    signals_read.extend(value.signals_read().clone());\n                    components_read.extend(value.components_read().clone());\n                }\n            }\n            Access { meta, var, access } => {\n                // Cache array index variable use.\n                for access in access.iter_mut() {\n                    if let AccessType::ArrayAccess(index) = access {\n                        index.cache_variable_use();\n                        locals_read.extend(index.locals_read().clone());\n                        signals_read.extend(index.signals_read().clone());\n                        components_read.extend(index.components_read().clone());\n                    }\n                }\n                // Match against the type of `var`.\n                match meta.type_knowledge().variable_type() {\n                    Some(VariableType::Local) => {\n                        trace!(\"adding `{var:?}` to local variables read\");\n                        locals_read.insert(VariableUse::new(meta, var, access));\n                    }\n                    Some(VariableType::Component | VariableType::AnonymousComponent) => {\n                        trace!(\"adding `{var:?}` to components read\");\n                        components_read.insert(VariableUse::new(meta, var, access));\n                    }\n                    Some(VariableType::Signal(_, _)) => {\n                        trace!(\"adding `{var:?}` to signals read\");\n                        signals_read.insert(VariableUse::new(meta, var, access));\n                    }\n                    None => {\n                        // If the variable type is unknown we ignore it.\n                        trace!(\"variable `{var:?}` of unknown type read\");\n                    }\n                }\n            }\n            Update { meta, var, access, rhe, .. } => {\n                // Cache RHS variable use.\n                rhe.cache_variable_use();\n                locals_read.extend(rhe.locals_read().iter().cloned());\n                signals_read.extend(rhe.signals_read().iter().cloned());\n                components_read.extend(rhe.components_read().iter().cloned());\n\n                // Cache array index variable use.\n                for access in access.iter_mut() {\n                    if let AccessType::ArrayAccess(index) = access {\n                        index.cache_variable_use();\n                        locals_read.extend(index.locals_read().clone());\n                        signals_read.extend(index.signals_read().clone());\n                        components_read.extend(index.components_read().clone());\n                    }\n                }\n                // Match against the type of `var`.\n                match meta.type_knowledge().variable_type() {\n                    Some(VariableType::Local) => {\n                        trace!(\"adding `{var:?}` to local variables read\");\n                        locals_read.insert(VariableUse::new(meta, var, &Vec::new()));\n                    }\n                    Some(VariableType::Component | VariableType::AnonymousComponent) => {\n                        trace!(\"adding `{var:?}` to components read\");\n                        components_read.insert(VariableUse::new(meta, var, &Vec::new()));\n                    }\n                    Some(VariableType::Signal(_, _)) => {\n                        trace!(\"adding `{var:?}` to signals read\");\n                        signals_read.insert(VariableUse::new(meta, var, &Vec::new()));\n                    }\n                    None => {\n                        // If the variable type is unknown we ignore it.\n                        trace!(\"variable `{var:?}` of unknown type read\");\n                    }\n                }\n            }\n            Number(_, _) => {}\n        }\n        self.meta_mut()\n            .variable_knowledge_mut()\n            .set_locals_read(&locals_read)\n            .set_locals_written(&VariableUses::new())\n            .set_signals_read(&signals_read)\n            .set_signals_written(&VariableUses::new())\n            .set_components_read(&components_read)\n            .set_components_written(&VariableUses::new());\n    }\n\n    fn locals_read(&self) -> &VariableUses {\n        self.meta().variable_knowledge().locals_read()\n    }\n\n    fn locals_written(&self) -> &VariableUses {\n        self.meta().variable_knowledge().locals_written()\n    }\n\n    fn signals_read(&self) -> &VariableUses {\n        self.meta().variable_knowledge().signals_read()\n    }\n\n    fn signals_written(&self) -> &VariableUses {\n        self.meta().variable_knowledge().signals_written()\n    }\n\n    fn components_read(&self) -> &VariableUses {\n        self.meta().variable_knowledge().components_read()\n    }\n\n    fn components_written(&self) -> &VariableUses {\n        self.meta().variable_knowledge().components_written()\n    }\n}\n\nimpl ValueMeta for Expression {\n    fn propagate_values(&mut self, env: &mut ValueEnvironment) -> bool {\n        use Expression::*;\n        use ValueReduction::*;\n        match self {\n            InfixOp { meta, lhe, infix_op, rhe, .. } => {\n                let mut result = lhe.propagate_values(env) || rhe.propagate_values(env);\n                if let Some(value) = infix_op.propagate_values(lhe.value(), rhe.value(), env) {\n                    result = result || meta.value_knowledge_mut().set_reduces_to(value)\n                }\n                result\n            }\n            PrefixOp { meta, prefix_op, rhe } => {\n                let mut result = rhe.propagate_values(env);\n                if let Some(value) = prefix_op.propagate_values(rhe.value(), env) {\n                    result = result || meta.value_knowledge_mut().set_reduces_to(value)\n                }\n                result\n            }\n            SwitchOp { meta, cond, if_true, if_false } => {\n                let mut result = cond.propagate_values(env)\n                    | if_true.propagate_values(env)\n                    | if_false.propagate_values(env);\n                match (cond.value(), if_true.value(), if_false.value()) {\n                    (\n                        // The case true? value: _\n                        Some(Boolean { value: cond }),\n                        Some(value),\n                        _,\n                    ) if *cond => {\n                        result = result || meta.value_knowledge_mut().set_reduces_to(value.clone())\n                    }\n                    (\n                        // The case false? _: value\n                        Some(Boolean { value: cond }),\n                        _,\n                        Some(value),\n                    ) if !cond => {\n                        result = result || meta.value_knowledge_mut().set_reduces_to(value.clone())\n                    }\n                    (\n                        // The case true? value: _\n                        Some(FieldElement { value: cond }),\n                        Some(value),\n                        _,\n                    ) if !cond.is_zero() => {\n                        result = result || meta.value_knowledge_mut().set_reduces_to(value.clone())\n                    }\n                    (\n                        // The case false? _: value\n                        Some(FieldElement { value: cond }),\n                        _,\n                        Some(value),\n                    ) if cond.is_zero() => {\n                        result = result || meta.value_knowledge_mut().set_reduces_to(value.clone())\n                    }\n                    _ => {}\n                }\n                result\n            }\n            Variable { meta, name, .. } => match env.get_variable(name) {\n                Some(value) => meta.value_knowledge_mut().set_reduces_to(value.clone()),\n                None => false,\n            },\n            Number(meta, value) => {\n                let value = FieldElement { value: value.clone() };\n                meta.value_knowledge_mut().set_reduces_to(value)\n            }\n            Call { args, .. } => {\n                // TODO: Handle function calls.\n                let mut result = false;\n                for arg in args {\n                    result = result || arg.propagate_values(env);\n                }\n                result\n            }\n            InlineArray { values, .. } => {\n                // TODO: Handle inline arrays.\n                let mut result = false;\n                for value in values {\n                    result = result || value.propagate_values(env);\n                }\n                result\n            }\n            Access { access, .. } => {\n                // TODO: Handle array values.\n                let mut result = false;\n                for access in access.iter_mut() {\n                    if let AccessType::ArrayAccess(index) = access {\n                        result = result || index.propagate_values(env);\n                    }\n                }\n                result\n            }\n            Update { access, rhe, .. } => {\n                // TODO: Handle array values.\n                let mut result = rhe.propagate_values(env);\n                for access in access.iter_mut() {\n                    if let AccessType::ArrayAccess(index) = access {\n                        result = result || index.propagate_values(env);\n                    }\n                }\n                result\n            }\n            Phi { meta, args, .. } => {\n                // Only set the value of the phi expression if all arguments agree on the value.\n                let values =\n                    args.iter().map(|name| env.get_variable(name)).collect::<Option<HashSet<_>>>();\n                match values {\n                    Some(values) if values.len() == 1 => {\n                        // This unwrap is safe since the size is non-zero.\n                        let value = *values.iter().next().unwrap();\n                        meta.value_knowledge_mut().set_reduces_to(value.clone())\n                    }\n                    _ => false,\n                }\n            }\n        }\n    }\n\n    fn is_constant(&self) -> bool {\n        self.value().is_some()\n    }\n\n    fn is_boolean(&self) -> bool {\n        matches!(self.value(), Some(ValueReduction::Boolean { .. }))\n    }\n\n    fn is_field_element(&self) -> bool {\n        matches!(self.value(), Some(ValueReduction::FieldElement { .. }))\n    }\n\n    fn value(&self) -> Option<&ValueReduction> {\n        self.meta().value_knowledge().get_reduces_to()\n    }\n}\n\nimpl ExpressionInfixOpcode {\n    fn propagate_degrees(\n        &self,\n        lhr: Option<&DegreeRange>,\n        rhr: Option<&DegreeRange>,\n    ) -> Option<DegreeRange> {\n        if let (Some(lhr), Some(rhr)) = (lhr, rhr) {\n            use ExpressionInfixOpcode::*;\n            match self {\n                Add => Some(lhr.add(rhr)),\n                Sub => Some(lhr.infix_sub(rhr)),\n                Mul => Some(lhr.mul(rhr)),\n                Pow => Some(lhr.pow(rhr)),\n                Div => Some(lhr.div(rhr)),\n                IntDiv => Some(lhr.int_div(rhr)),\n                Mod => Some(lhr.modulo(rhr)),\n                ShiftL => Some(lhr.shift_left(rhr)),\n                ShiftR => Some(lhr.shift_right(rhr)),\n                Lesser => Some(lhr.lesser(rhr)),\n                Greater => Some(lhr.greater(rhr)),\n                LesserEq => Some(lhr.lesser_eq(rhr)),\n                GreaterEq => Some(lhr.greater_eq(rhr)),\n                Eq => Some(lhr.equal(rhr)),\n                NotEq => Some(lhr.not_equal(rhr)),\n                BitOr => Some(lhr.bit_or(rhr)),\n                BitXor => Some(lhr.bit_xor(rhr)),\n                BitAnd => Some(lhr.bit_and(rhr)),\n                BoolOr => Some(lhr.bool_or(rhr)),\n                BoolAnd => Some(lhr.bool_and(rhr)),\n            }\n        } else {\n            None\n        }\n    }\n\n    fn propagate_values(\n        &self,\n        lhv: Option<&ValueReduction>,\n        rhv: Option<&ValueReduction>,\n        env: &ValueEnvironment,\n    ) -> Option<ValueReduction> {\n        let p = env.prime();\n\n        use ValueReduction::*;\n        match (lhv, rhv) {\n            // lhv and rhv reduce to two field elements.\n            (Some(FieldElement { value: lhv }), Some(FieldElement { value: rhv })) => {\n                use ExpressionInfixOpcode::*;\n                match self {\n                    Mul => {\n                        let value = modular_arithmetic::mul(lhv, rhv, p);\n                        Some(FieldElement { value })\n                    }\n                    Div => modular_arithmetic::div(lhv, rhv, p)\n                        .ok()\n                        .map(|value| FieldElement { value }),\n                    Add => {\n                        let value = modular_arithmetic::add(lhv, rhv, p);\n                        Some(FieldElement { value })\n                    }\n                    Sub => {\n                        let value = modular_arithmetic::sub(lhv, rhv, p);\n                        Some(FieldElement { value })\n                    }\n                    Pow => {\n                        let value = modular_arithmetic::pow(lhv, rhv, p);\n                        Some(FieldElement { value })\n                    }\n                    IntDiv => modular_arithmetic::idiv(lhv, rhv, p)\n                        .ok()\n                        .map(|value| FieldElement { value }),\n                    Mod => modular_arithmetic::mod_op(lhv, rhv, p)\n                        .ok()\n                        .map(|value| FieldElement { value }),\n                    ShiftL => modular_arithmetic::shift_l(lhv, rhv, p)\n                        .ok()\n                        .map(|value| FieldElement { value }),\n                    ShiftR => modular_arithmetic::shift_r(lhv, rhv, p)\n                        .ok()\n                        .map(|value| FieldElement { value }),\n                    LesserEq => {\n                        let value = modular_arithmetic::lesser_eq(lhv, rhv, p);\n                        Some(Boolean { value: modular_arithmetic::as_bool(&value, p) })\n                    }\n                    GreaterEq => {\n                        let value = modular_arithmetic::greater_eq(lhv, rhv, p);\n                        Some(Boolean { value: modular_arithmetic::as_bool(&value, p) })\n                    }\n                    Lesser => {\n                        let value = modular_arithmetic::lesser(lhv, rhv, p);\n                        Some(Boolean { value: modular_arithmetic::as_bool(&value, p) })\n                    }\n                    Greater => {\n                        let value = modular_arithmetic::greater(lhv, rhv, p);\n                        Some(Boolean { value: modular_arithmetic::as_bool(&value, p) })\n                    }\n                    Eq => {\n                        let value = modular_arithmetic::eq(lhv, rhv, p);\n                        Some(Boolean { value: modular_arithmetic::as_bool(&value, p) })\n                    }\n                    NotEq => {\n                        let value = modular_arithmetic::not_eq(lhv, rhv, p);\n                        Some(Boolean { value: modular_arithmetic::as_bool(&value, p) })\n                    }\n                    BitOr => {\n                        let value = modular_arithmetic::bit_or(lhv, rhv, p);\n                        Some(FieldElement { value })\n                    }\n                    BitAnd => {\n                        let value = modular_arithmetic::bit_and(lhv, rhv, p);\n                        Some(FieldElement { value })\n                    }\n                    BitXor => {\n                        let value = modular_arithmetic::bit_xor(lhv, rhv, p);\n                        Some(FieldElement { value })\n                    }\n                    // Remaining operations do not make sense.\n                    // TODO: Add report/error propagation here.\n                    _ => None,\n                }\n            }\n            // lhv and rhv reduce to two booleans.\n            (Some(Boolean { value: lhv }), Some(Boolean { value: rhv })) => {\n                use ExpressionInfixOpcode::*;\n                match self {\n                    BoolAnd => Some(Boolean { value: *lhv && *rhv }),\n                    BoolOr => Some(Boolean { value: *lhv || *rhv }),\n                    // Remaining operations do not make sense.\n                    // TODO: Add report propagation here as well.\n                    _ => None,\n                }\n            }\n            _ => None,\n        }\n    }\n}\n\nimpl ExpressionPrefixOpcode {\n    fn propagate_degrees(&self, range: Option<&DegreeRange>) -> Option<DegreeRange> {\n        if let Some(range) = range {\n            use ExpressionPrefixOpcode::*;\n            match self {\n                Sub => Some(range.prefix_sub()),\n                Complement => Some(range.complement()),\n                BoolNot => Some(range.bool_not()),\n            }\n        } else {\n            None\n        }\n    }\n\n    fn propagate_values(\n        &self,\n        rhe: Option<&ValueReduction>,\n        env: &ValueEnvironment,\n    ) -> Option<ValueReduction> {\n        let p = env.prime();\n\n        use ValueReduction::*;\n        match rhe {\n            // arg reduces to a field element.\n            Some(FieldElement { value: arg }) => {\n                use ExpressionPrefixOpcode::*;\n                match self {\n                    Sub => {\n                        let value = modular_arithmetic::prefix_sub(arg, p);\n                        Some(FieldElement { value })\n                    }\n                    Complement => {\n                        let value = modular_arithmetic::complement_256(arg, p);\n                        Some(FieldElement { value })\n                    }\n                    // Remaining operations do not make sense.\n                    // TODO: Add report propagation here as well.\n                    _ => None,\n                }\n            }\n            // arg reduces to a boolean.\n            Some(Boolean { value: arg }) => {\n                use ExpressionPrefixOpcode::*;\n                match self {\n                    BoolNot => Some(Boolean { value: !arg }),\n                    // Remaining operations do not make sense.\n                    // TODO: Add report propagation here as well.\n                    _ => None,\n                }\n            }\n            None => None,\n        }\n    }\n}\n\nimpl fmt::Debug for Expression {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        use Expression::*;\n        match self {\n            Number(_, value) => write!(f, \"{value}\"),\n            Variable { name, .. } => {\n                write!(f, \"{name:?}\")\n            }\n            InfixOp { lhe, infix_op, rhe, .. } => write!(f, \"({lhe:?} {infix_op} {rhe:?})\"),\n            PrefixOp { prefix_op, rhe, .. } => write!(f, \"({prefix_op}{rhe:?})\"),\n            SwitchOp { cond, if_true, if_false, .. } => {\n                write!(f, \"({cond:?}? {if_true:?} : {if_false:?})\")\n            }\n            Call { name: id, args, .. } => write!(f, \"{}({})\", id, vec_to_debug(args, \", \")),\n            InlineArray { values, .. } => write!(f, \"[{}]\", vec_to_debug(values, \", \")),\n            Access { var, access, .. } => {\n                let access = access\n                    .iter()\n                    .map(|access| match access {\n                        AccessType::ArrayAccess(index) => format!(\"{index:?}\"),\n                        AccessType::ComponentAccess(name) => name.clone(),\n                    })\n                    .collect::<Vec<_>>()\n                    .join(\", \");\n                write!(f, \"access({var:?}, [{access}])\")\n            }\n            Update { var, access, rhe, .. } => {\n                let access = access\n                    .iter()\n                    .map(|access| match access {\n                        AccessType::ArrayAccess(index) => format!(\"{index:?}\"),\n                        AccessType::ComponentAccess(name) => name.clone(),\n                    })\n                    .collect::<Vec<_>>()\n                    .join(\", \");\n                write!(f, \"update({var:?}, [{access}], {rhe:?})\")\n            }\n            Phi { args, .. } => write!(f, \"φ({})\", vec_to_debug(args, \", \")),\n        }\n    }\n}\n\nimpl fmt::Display for Expression {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        use Expression::*;\n        match self {\n            Number(_, value) => write!(f, \"{value}\"),\n            Variable { name, .. } => {\n                write!(f, \"{name}\")\n            }\n            InfixOp { lhe, infix_op, rhe, .. } => write!(f, \"({lhe} {infix_op} {rhe})\"),\n            PrefixOp { prefix_op, rhe, .. } => write!(f, \"{prefix_op}({rhe})\"),\n            SwitchOp { cond, if_true, if_false, .. } => {\n                write!(f, \"({cond}? {if_true} : {if_false})\")\n            }\n            Call { name: id, args, .. } => write!(f, \"{}({})\", id, vec_to_display(args, \", \")),\n            InlineArray { values, .. } => write!(f, \"[{}]\", vec_to_display(values, \", \")),\n            Access { var, access, .. } => {\n                write!(f, \"{var}\")?;\n                for access in access {\n                    write!(f, \"{access}\")?;\n                }\n                Ok(())\n            }\n            Update { rhe, .. } => {\n                // `Update` nodes are handled at the statement level. If we are\n                // trying to display the RHS of an array assignment, we probably\n                // want the `rhe` input.\n                write!(f, \"{rhe}\")\n            }\n            Phi { args, .. } => write!(f, \"φ({})\", vec_to_display(args, \", \")),\n        }\n    }\n}\n\nimpl fmt::Display for ExpressionInfixOpcode {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        use ExpressionInfixOpcode::*;\n        match self {\n            Mul => f.write_str(\"*\"),\n            Div => f.write_str(\"/\"),\n            Add => f.write_str(\"+\"),\n            Sub => f.write_str(\"-\"),\n            Pow => f.write_str(\"**\"),\n            IntDiv => f.write_str(\"\\\\\"),\n            Mod => f.write_str(\"%\"),\n            ShiftL => f.write_str(\"<<\"),\n            ShiftR => f.write_str(\">>\"),\n            LesserEq => f.write_str(\"<=\"),\n            GreaterEq => f.write_str(\">=\"),\n            Lesser => f.write_str(\"<\"),\n            Greater => f.write_str(\">\"),\n            Eq => f.write_str(\"==\"),\n            NotEq => f.write_str(\"!=\"),\n            BoolOr => f.write_str(\"||\"),\n            BoolAnd => f.write_str(\"&&\"),\n            BitOr => f.write_str(\"|\"),\n            BitAnd => f.write_str(\"&\"),\n            BitXor => f.write_str(\"^\"),\n        }\n    }\n}\n\nimpl fmt::Display for ExpressionPrefixOpcode {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        use ExpressionPrefixOpcode::*;\n        match self {\n            Sub => f.write_str(\"-\"),\n            BoolNot => f.write_str(\"!\"),\n            Complement => f.write_str(\"~\"),\n        }\n    }\n}\n\nimpl fmt::Debug for AccessType {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        use AccessType::*;\n        match self {\n            ArrayAccess(index) => write!(f, \"{index:?}\"),\n            ComponentAccess(name) => write!(f, \"{name:?}\"),\n        }\n    }\n}\n\nimpl fmt::Display for AccessType {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        use AccessType::*;\n        match self {\n            ArrayAccess(index) => write!(f, \"[{index}]\"),\n            ComponentAccess(name) => write!(f, \".{name}\"),\n        }\n    }\n}\n\n#[must_use]\nfn vec_to_debug<T: fmt::Debug>(elems: &[T], sep: &str) -> String {\n    elems.iter().map(|elem| format!(\"{elem:?}\")).collect::<Vec<String>>().join(sep)\n}\n\n#[must_use]\nfn vec_to_display<T: fmt::Display>(elems: &[T], sep: &str) -> String {\n    elems.iter().map(|elem| format!(\"{elem}\")).collect::<Vec<String>>().join(sep)\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::constants::{UsefulConstants, Curve};\n\n    use super::*;\n\n    #[test]\n    fn test_propagate_values() {\n        use Expression::*;\n        use ExpressionInfixOpcode::*;\n        use ValueReduction::*;\n        let mut lhe = Number(Meta::default(), 7u64.into());\n        let mut rhe = Variable { meta: Meta::default(), name: VariableName::from_string(\"v\") };\n        let constants = UsefulConstants::new(&Curve::default());\n        let mut env = ValueEnvironment::new(&constants);\n        env.add_variable(&VariableName::from_string(\"v\"), &FieldElement { value: 3u64.into() });\n        lhe.propagate_values(&mut env);\n        rhe.propagate_values(&mut env);\n\n        // Infix multiplication.\n        let mut expr = InfixOp {\n            meta: Meta::default(),\n            infix_op: Mul,\n            lhe: Box::new(lhe.clone()),\n            rhe: Box::new(rhe.clone()),\n        };\n        expr.propagate_values(&mut env.clone());\n        assert_eq!(expr.value(), Some(&FieldElement { value: 21u64.into() }));\n\n        // Infix addition.\n        let mut expr = InfixOp {\n            meta: Meta::default(),\n            infix_op: Add,\n            lhe: Box::new(lhe.clone()),\n            rhe: Box::new(rhe.clone()),\n        };\n        expr.propagate_values(&mut env.clone());\n        assert_eq!(expr.value(), Some(&FieldElement { value: 10u64.into() }));\n\n        // Infix integer division.\n        let mut expr = InfixOp {\n            meta: Meta::default(),\n            infix_op: IntDiv,\n            lhe: Box::new(lhe.clone()),\n            rhe: Box::new(rhe.clone()),\n        };\n        expr.propagate_values(&mut env.clone());\n        assert_eq!(expr.value(), Some(&FieldElement { value: 2u64.into() }));\n    }\n}\n"
  },
  {
    "path": "program_structure/src/intermediate_representation/ir.rs",
    "content": "use num_bigint::BigInt;\nuse std::fmt;\n\nuse crate::file_definition::{FileID, FileLocation};\nuse crate::nonempty_vec::NonEmptyVec;\n\nuse super::degree_meta::DegreeKnowledge;\nuse super::type_meta::TypeKnowledge;\nuse super::value_meta::ValueKnowledge;\nuse super::variable_meta::VariableKnowledge;\n\ntype Index = usize;\ntype Version = usize;\n\n#[derive(Clone, Default)]\npub struct Meta {\n    pub location: FileLocation,\n    pub file_id: Option<FileID>,\n    degree_knowledge: DegreeKnowledge,\n    type_knowledge: TypeKnowledge,\n    value_knowledge: ValueKnowledge,\n    variable_knowledge: VariableKnowledge,\n}\n\nimpl Meta {\n    #[must_use]\n    pub fn new(location: &FileLocation, file_id: &Option<FileID>) -> Meta {\n        Meta {\n            location: location.clone(),\n            file_id: *file_id,\n            degree_knowledge: DegreeKnowledge::default(),\n            type_knowledge: TypeKnowledge::default(),\n            value_knowledge: ValueKnowledge::default(),\n            variable_knowledge: VariableKnowledge::default(),\n        }\n    }\n\n    #[must_use]\n    pub fn start(&self) -> usize {\n        self.location.start\n    }\n\n    #[must_use]\n    pub fn end(&self) -> usize {\n        self.location.end\n    }\n\n    #[must_use]\n    pub fn file_id(&self) -> Option<FileID> {\n        self.file_id\n    }\n\n    #[must_use]\n    pub fn file_location(&self) -> FileLocation {\n        self.location.clone()\n    }\n\n    #[must_use]\n    pub fn degree_knowledge(&self) -> &DegreeKnowledge {\n        &self.degree_knowledge\n    }\n\n    #[must_use]\n    pub fn type_knowledge(&self) -> &TypeKnowledge {\n        &self.type_knowledge\n    }\n\n    #[must_use]\n    pub fn value_knowledge(&self) -> &ValueKnowledge {\n        &self.value_knowledge\n    }\n\n    #[must_use]\n    pub fn variable_knowledge(&self) -> &VariableKnowledge {\n        &self.variable_knowledge\n    }\n\n    #[must_use]\n    pub fn degree_knowledge_mut(&mut self) -> &mut DegreeKnowledge {\n        &mut self.degree_knowledge\n    }\n\n    #[must_use]\n    pub fn type_knowledge_mut(&mut self) -> &mut TypeKnowledge {\n        &mut self.type_knowledge\n    }\n\n    #[must_use]\n    pub fn value_knowledge_mut(&mut self) -> &mut ValueKnowledge {\n        &mut self.value_knowledge\n    }\n\n    #[must_use]\n    pub fn variable_knowledge_mut(&mut self) -> &mut VariableKnowledge {\n        &mut self.variable_knowledge\n    }\n}\n\nimpl std::hash::Hash for Meta {\n    fn hash<H>(&self, state: &mut H)\n    where\n        H: std::hash::Hasher,\n    {\n        self.location.hash(state);\n        self.file_id.hash(state);\n        state.finish();\n    }\n}\n\nimpl PartialEq for Meta {\n    fn eq(&self, other: &Meta) -> bool {\n        self.location == other.location && self.file_id == other.file_id\n    }\n}\n\nimpl Eq for Meta {}\n\n// TODO: Implement a custom `PartialEq` for `Statement`.\n#[derive(Clone)]\n#[allow(clippy::large_enum_variant)]\npub enum Statement {\n    // We allow for declarations of multiple variables of the same type to avoid\n    // having to insert new declarations when converting the CFG to SSA.\n    Declaration {\n        meta: Meta,\n        names: NonEmptyVec<VariableName>,\n        var_type: VariableType,\n        dimensions: Vec<Expression>,\n    },\n    IfThenElse {\n        meta: Meta,\n        cond: Expression,\n        true_index: Index,\n        false_index: Option<Index>,\n    },\n    Return {\n        meta: Meta,\n        value: Expression,\n    },\n    // Array and component signal assignments (where `access` is non-empty) are\n    // rewritten using `Update` expressions. This allows us to track version\n    // information when transforming the CFG to SSA form.\n    //\n    // Note: The type metadata in `meta` tracks the type of the variable `var`.\n    Substitution {\n        meta: Meta,\n        var: VariableName,\n        op: AssignOp,\n        rhe: Expression,\n    },\n    ConstraintEquality {\n        meta: Meta,\n        lhe: Expression,\n        rhe: Expression,\n    },\n    LogCall {\n        meta: Meta,\n        args: Vec<LogArgument>,\n    },\n    Assert {\n        meta: Meta,\n        arg: Expression,\n    },\n}\n\n#[derive(Clone)]\npub enum Expression {\n    /// An infix operation of the form `lhe * rhe`.\n    InfixOp {\n        meta: Meta,\n        lhe: Box<Expression>,\n        infix_op: ExpressionInfixOpcode,\n        rhe: Box<Expression>,\n    },\n    /// A prefix operation of the form `* rhe`.\n    PrefixOp { meta: Meta, prefix_op: ExpressionPrefixOpcode, rhe: Box<Expression> },\n    /// An inline switch operation (or inline if-then-else) of the form `cond?\n    /// if_true: if_false`.\n    SwitchOp {\n        meta: Meta,\n        cond: Box<Expression>,\n        if_true: Box<Expression>,\n        if_false: Box<Expression>,\n    },\n    /// A local variable, signal, or component.\n    Variable { meta: Meta, name: VariableName },\n    /// A constant field element.\n    Number(Meta, BigInt),\n    /// A function call node.\n    Call { meta: Meta, name: String, args: Vec<Expression> },\n    /// An inline array on the form `[value, ...]`.\n    InlineArray { meta: Meta, values: Vec<Expression> },\n    /// An `Access` node represents an array access of the form `a[i]...[k]`.\n    Access { meta: Meta, var: VariableName, access: Vec<AccessType> },\n    /// Updates of the form `var[i]...[k] = rhe` lift to IR statements of the\n    /// form `var = update(var, (i, ..., k), rhe)`. This is needed when we\n    /// convert the CFG to SSA. Since arrays are versioned atomically, we need\n    /// to track which version of the array that is updated to obtain the new\n    /// version. (This is needed to track variable use, dead assignments, and\n    /// data flow.)\n    ///\n    /// Note: The type metadata in `meta` tracks the type of the variable `var`.\n    Update { meta: Meta, var: VariableName, access: Vec<AccessType>, rhe: Box<Expression> },\n    /// An SSA phi-expression.\n    Phi { meta: Meta, args: Vec<VariableName> },\n}\n\npub type TagList = Vec<String>;\n\n#[derive(Clone, PartialEq, Eq, Hash)]\npub enum VariableType {\n    Local,\n    Component,\n    AnonymousComponent,\n    Signal(SignalType, TagList),\n}\n\nimpl fmt::Display for VariableType {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        use SignalType::*;\n        use VariableType::*;\n        match self {\n            Local => write!(f, \"var\"),\n            AnonymousComponent | Component => write!(f, \"component\"),\n            Signal(signal_type, tag_list) => {\n                if matches!(signal_type, Intermediate) {\n                    write!(f, \"signal\")?;\n                } else {\n                    write!(f, \"signal {signal_type}\")?;\n                }\n                if !tag_list.is_empty() {\n                    write!(f, \" {{{}}}\", tag_list.join(\", \"))\n                } else {\n                    Ok(())\n                }\n            }\n        }\n    }\n}\n\n#[derive(Copy, Clone, PartialEq, Eq, Hash)]\npub enum SignalType {\n    Input,\n    Output,\n    Intermediate,\n}\n\nimpl fmt::Display for SignalType {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        use SignalType::*;\n        match self {\n            Input => write!(f, \"input\"),\n            Output => write!(f, \"output\"),\n            Intermediate => Ok(()), // Intermediate signals have no explicit signal type.\n        }\n    }\n}\n\n/// A IR variable name consists of three components.\n///\n///   1. The original name (obtained from the source code).\n///   2. An optional suffix (used to ensure uniqueness when lifting to IR).\n///   3. An optional version (applied when the CFG is converted to SSA form).\n#[derive(Clone, Hash, PartialEq, Eq)]\npub struct VariableName {\n    /// This is the original name of the variable from the function or template\n    /// AST.\n    name: String,\n    /// For shadowing declarations we need to rename the shadowing variable\n    /// since construction of the CFG requires all variable names to be unique.\n    /// This is done by adding a suffix (on the form `_n`) to the variable name.\n    suffix: Option<String>,\n    /// The version is used to track variable versions when we convert the CFG\n    /// to SSA.\n    version: Option<Version>,\n}\n\nimpl VariableName {\n    /// Returns a new variable name with the given name (without suffix or version).\n    #[must_use]\n    pub fn from_string<N: ToString>(name: N) -> VariableName {\n        VariableName { name: name.to_string(), suffix: None, version: None }\n    }\n\n    #[must_use]\n    pub fn name(&self) -> &String {\n        &self.name\n    }\n\n    #[must_use]\n    pub fn suffix(&self) -> &Option<String> {\n        &self.suffix\n    }\n\n    #[must_use]\n    pub fn version(&self) -> &Option<Version> {\n        &self.version\n    }\n\n    /// Returns a new copy of the variable name, adding the given suffix.\n    #[must_use]\n    pub fn with_suffix<S: ToString>(&self, suffix: S) -> VariableName {\n        let mut result = self.clone();\n        result.suffix = Some(suffix.to_string());\n        result\n    }\n\n    /// Returns a new copy of the variable name, adding the given version.\n    #[must_use]\n    pub fn with_version(&self, version: Version) -> VariableName {\n        let mut result = self.clone();\n        result.version = Some(version);\n        result\n    }\n\n    /// Returns a new copy of the variable name with the suffix dropped.\n    #[must_use]\n    pub fn without_suffix(&self) -> VariableName {\n        let mut result = self.clone();\n        result.suffix = None;\n        result\n    }\n\n    /// Returns a new copy of the variable name with the version dropped.\n    #[must_use]\n    pub fn without_version(&self) -> VariableName {\n        let mut result = self.clone();\n        result.version = None;\n        result\n    }\n}\n\n/// Display for VariableName only outputs the original name.\nimpl fmt::Display for VariableName {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        write!(f, \"{}\", self.name)\n    }\n}\n\n/// Debug for VariableName outputs the full name (including suffix and version).\nimpl fmt::Debug for VariableName {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        write!(f, \"{}\", self.name)?;\n        if let Some(suffix) = self.suffix() {\n            write!(f, \"_{suffix}\")?;\n        }\n        if let Some(version) = self.version() {\n            write!(f, \".{version}\")?;\n        }\n        Ok(())\n    }\n}\n\n#[derive(Clone, Hash, Eq, PartialEq)]\npub enum AccessType {\n    ArrayAccess(Box<Expression>),\n    ComponentAccess(String),\n}\n\n#[derive(Copy, Clone, Hash, Eq, PartialEq)]\npub enum AssignOp {\n    /// A signal assignment (using `<--`)\n    AssignSignal,\n    /// A signal assignment (using `<==`)\n    AssignConstraintSignal,\n    /// A local variable assignment or component initialization (using `=`).\n    AssignLocalOrComponent,\n}\n\n#[derive(Copy, Clone, Hash, Eq, PartialEq)]\npub enum ExpressionInfixOpcode {\n    Mul,\n    Div,\n    Add,\n    Sub,\n    Pow,\n    IntDiv,\n    Mod,\n    ShiftL,\n    ShiftR,\n    LesserEq,\n    GreaterEq,\n    Lesser,\n    Greater,\n    Eq,\n    NotEq,\n    BoolOr,\n    BoolAnd,\n    BitOr,\n    BitAnd,\n    BitXor,\n}\n\n#[derive(Copy, Clone, Hash, Eq, PartialEq)]\npub enum ExpressionPrefixOpcode {\n    Sub,\n    BoolNot,\n    Complement,\n}\n\n#[derive(Clone)]\npub enum LogArgument {\n    String(String),\n    Expr(Box<Expression>),\n}\n"
  },
  {
    "path": "program_structure/src/intermediate_representation/lifting.rs",
    "content": "use crate::ast::{self, LogArgument};\nuse crate::report::ReportCollection;\n\nuse crate::ir;\nuse crate::ir::declarations::{Declaration, Declarations};\nuse crate::ir::errors::{IRError, IRResult};\nuse crate::nonempty_vec::NonEmptyVec;\n\n/// The `TryLift` trait is used to lift an AST node to an IR node. This may fail\n/// and produce an error. Even if the operation succeeds it may produce warnings\n/// which in this case are added to the report collection.\npub(crate) trait TryLift<Context> {\n    type IR;\n    type Error;\n\n    /// Generate a corresponding IR node of type `Self::IR` from an AST node.\n    fn try_lift(\n        &self,\n        context: Context,\n        reports: &mut ReportCollection,\n    ) -> Result<Self::IR, Self::Error>;\n}\n\n#[derive(Default)]\npub(crate) struct LiftingEnvironment {\n    /// Tracks all variable declarations.\n    declarations: Declarations,\n}\n\nimpl LiftingEnvironment {\n    #[must_use]\n    pub fn new() -> LiftingEnvironment {\n        LiftingEnvironment::default()\n    }\n\n    pub fn add_declaration(&mut self, declaration: &Declaration) {\n        self.declarations.add_declaration(declaration);\n    }\n}\n\nimpl From<LiftingEnvironment> for Declarations {\n    fn from(env: LiftingEnvironment) -> Declarations {\n        env.declarations\n    }\n}\n\n// Attempt to convert an AST statement into an IR statement. This will fail on\n// statements that need to be handled manually (like `While`, `IfThenElse`, and\n// `MultiSubstitution`), as well as statements that have no direct IR\n// counterparts (like `Declaration`, `Block`, and `InitializationBlock`).\nimpl TryLift<()> for ast::Statement {\n    type IR = ir::Statement;\n    type Error = IRError;\n\n    fn try_lift(&self, _: (), reports: &mut ReportCollection) -> IRResult<Self::IR> {\n        match self {\n            ast::Statement::Return { meta, value } => Ok(ir::Statement::Return {\n                meta: meta.try_lift((), reports)?,\n                value: value.try_lift((), reports)?,\n            }),\n            ast::Statement::Substitution { meta, var, op, rhe, access } => {\n                // If this is an array or component signal assignment (i.e. when access\n                // is non-empty), the RHS is lifted to an `Update` expression.\n                let rhe = if access.is_empty() {\n                    rhe.try_lift((), reports)?\n                } else {\n                    ir::Expression::Update {\n                        meta: meta.try_lift((), reports)?,\n                        var: var.try_lift(meta, reports)?,\n                        access: access\n                            .iter()\n                            .map(|access| access.try_lift((), reports))\n                            .collect::<IRResult<Vec<ir::AccessType>>>()?,\n                        rhe: Box::new(rhe.try_lift((), reports)?),\n                    }\n                };\n                Ok(ir::Statement::Substitution {\n                    meta: meta.try_lift((), reports)?,\n                    var: var.try_lift(meta, reports)?,\n                    op: op.try_lift((), reports)?,\n                    rhe,\n                })\n            }\n            ast::Statement::ConstraintEquality { meta, lhe, rhe } => {\n                Ok(ir::Statement::ConstraintEquality {\n                    meta: meta.try_lift((), reports)?,\n                    lhe: lhe.try_lift((), reports)?,\n                    rhe: rhe.try_lift((), reports)?,\n                })\n            }\n            ast::Statement::LogCall { meta, args } => Ok(ir::Statement::LogCall {\n                meta: meta.try_lift((), reports)?,\n                args: args\n                    .iter()\n                    .map(|arg| arg.try_lift((), reports))\n                    .collect::<IRResult<Vec<_>>>()?,\n            }),\n            ast::Statement::Assert { meta, arg } => Ok(ir::Statement::Assert {\n                meta: meta.try_lift((), reports)?,\n                arg: arg.try_lift((), reports)?,\n            }),\n            ast::Statement::Declaration { meta, xtype, name, dimensions, .. } => {\n                Ok(ir::Statement::Declaration {\n                    meta: meta.try_lift((), reports)?,\n                    names: NonEmptyVec::new(name.try_lift(meta, reports)?),\n                    var_type: xtype.try_lift((), reports)?,\n                    dimensions: dimensions\n                        .iter()\n                        .map(|size| size.try_lift((), reports))\n                        .collect::<IRResult<Vec<_>>>()?,\n                })\n            }\n            ast::Statement::Block { .. }\n            | ast::Statement::While { .. }\n            | ast::Statement::IfThenElse { .. }\n            | ast::Statement::MultiSubstitution { .. }\n            | ast::Statement::InitializationBlock { .. } => {\n                // These need to be handled by the caller.\n                panic!(\"failed to convert AST statement to IR\")\n            }\n        }\n    }\n}\n\n// Attempt to convert an AST expression to an IR expression. This will fail on\n// expressions that need to be handled directly by the caller (like `Tuple` and\n// `AnonymousComponent`).\nimpl TryLift<()> for ast::Expression {\n    type IR = ir::Expression;\n    type Error = IRError;\n\n    fn try_lift(&self, _: (), reports: &mut ReportCollection) -> IRResult<Self::IR> {\n        match self {\n            ast::Expression::InfixOp { meta, lhe, infix_op, rhe } => Ok(ir::Expression::InfixOp {\n                meta: meta.try_lift((), reports)?,\n                lhe: Box::new(lhe.try_lift((), reports)?),\n                infix_op: infix_op.try_lift((), reports)?,\n                rhe: Box::new(rhe.try_lift((), reports)?),\n            }),\n            ast::Expression::PrefixOp { meta, prefix_op, rhe } => Ok(ir::Expression::PrefixOp {\n                meta: meta.try_lift((), reports)?,\n                prefix_op: prefix_op.try_lift((), reports)?,\n                rhe: Box::new(rhe.try_lift((), reports)?),\n            }),\n            ast::Expression::InlineSwitchOp { meta, cond, if_true, if_false } => {\n                Ok(ir::Expression::SwitchOp {\n                    meta: meta.try_lift((), reports)?,\n                    cond: Box::new(cond.try_lift((), reports)?),\n                    if_true: Box::new(if_true.try_lift((), reports)?),\n                    if_false: Box::new(if_false.try_lift((), reports)?),\n                })\n            }\n            ast::Expression::Variable { meta, name, access } => {\n                if access.is_empty() {\n                    Ok(ir::Expression::Variable {\n                        meta: meta.try_lift((), reports)?,\n                        name: name.try_lift(meta, reports)?,\n                    })\n                } else {\n                    Ok(ir::Expression::Access {\n                        meta: meta.try_lift((), reports)?,\n                        var: name.try_lift(meta, reports)?,\n                        access: access\n                            .iter()\n                            .map(|access| access.try_lift((), reports))\n                            .collect::<IRResult<Vec<ir::AccessType>>>()?,\n                    })\n                }\n            }\n            ast::Expression::Number(meta, value) => {\n                Ok(ir::Expression::Number(meta.try_lift((), reports)?, value.clone()))\n            }\n            ast::Expression::Call { meta, id, args } => Ok(ir::Expression::Call {\n                meta: meta.try_lift((), reports)?,\n                name: id.clone(),\n                args: args\n                    .iter()\n                    .map(|arg| arg.try_lift((), reports))\n                    .collect::<IRResult<Vec<ir::Expression>>>()?,\n            }),\n            ast::Expression::ArrayInLine { meta, values } => Ok(ir::Expression::InlineArray {\n                meta: meta.try_lift((), reports)?,\n                values: values\n                    .iter()\n                    .map(|value| value.try_lift((), reports))\n                    .collect::<IRResult<Vec<ir::Expression>>>()?,\n            }),\n            // TODO: We currently treat `ParallelOp` as transparent and simply\n            // lift the underlying expression. Should this be added to the IR?\n            ast::Expression::ParallelOp { rhe, .. } => rhe.try_lift((), reports),\n            ast::Expression::Tuple { .. } | ast::Expression::AnonymousComponent { .. } => {\n                // These need to be handled by the caller.\n                panic!(\"failed to convert AST expression to IR\")\n            }\n        }\n    }\n}\n\n// Convert AST metadata to IR metadata. (This will always succeed.)\nimpl TryLift<()> for ast::Meta {\n    type IR = ir::Meta;\n    type Error = IRError;\n\n    fn try_lift(&self, _: (), _: &mut ReportCollection) -> IRResult<Self::IR> {\n        Ok(ir::Meta::new(&self.location, &self.file_id))\n    }\n}\n\n// Convert an AST variable type to an IR type. (This will always succeed.)\nimpl TryLift<()> for ast::VariableType {\n    type IR = ir::VariableType;\n    type Error = IRError;\n\n    fn try_lift(&self, _: (), reports: &mut ReportCollection) -> IRResult<Self::IR> {\n        match self {\n            ast::VariableType::Var => Ok(ir::VariableType::Local),\n            ast::VariableType::Component => Ok(ir::VariableType::Component),\n            ast::VariableType::AnonymousComponent => Ok(ir::VariableType::AnonymousComponent),\n            ast::VariableType::Signal(signal_type, tag_list) => {\n                Ok(ir::VariableType::Signal(signal_type.try_lift((), reports)?, tag_list.clone()))\n            }\n        }\n    }\n}\n\n// Convert an AST signal type to an IR signal type. (This will always succeed.)\nimpl TryLift<()> for ast::SignalType {\n    type IR = ir::SignalType;\n    type Error = IRError;\n\n    fn try_lift(&self, _: (), _: &mut ReportCollection) -> IRResult<Self::IR> {\n        match self {\n            ast::SignalType::Input => Ok(ir::SignalType::Input),\n            ast::SignalType::Output => Ok(ir::SignalType::Output),\n            ast::SignalType::Intermediate => Ok(ir::SignalType::Intermediate),\n        }\n    }\n}\n\n// Attempt to convert a string to an IR variable name.\nimpl TryLift<&ast::Meta> for String {\n    type IR = ir::VariableName;\n    type Error = IRError;\n\n    fn try_lift(&self, meta: &ast::Meta, _: &mut ReportCollection) -> IRResult<Self::IR> {\n        // We assume that the input string uses '.' to separate the name from the suffix.\n        let tokens: Vec<_> = self.split('.').collect();\n        match tokens.len() {\n            1 => Ok(ir::VariableName::from_string(tokens[0])),\n            2 => Ok(ir::VariableName::from_string(tokens[0]).with_suffix(tokens[1])),\n            // Either the original name from the AST contains `.`, or the suffix\n            // added when ensuring uniqueness contains `.`. Neither case should\n            // occur, so we return an error here instead of producing a report.\n            _ => Err(IRError::InvalidVariableNameError {\n                name: self.clone(),\n                file_id: meta.file_id,\n                file_location: meta.location.clone(),\n            }),\n        }\n    }\n}\n\n// Convert an AST access to an IR access. (This will always succeed.)\nimpl TryLift<()> for ast::Access {\n    type IR = ir::AccessType;\n    type Error = IRError;\n\n    fn try_lift(&self, _: (), reports: &mut ReportCollection) -> IRResult<Self::IR> {\n        match self {\n            ast::Access::ArrayAccess(expr) => {\n                Ok(ir::AccessType::ArrayAccess(Box::new(expr.try_lift((), reports)?)))\n            }\n            ast::Access::ComponentAccess(s) => Ok(ir::AccessType::ComponentAccess(s.clone())),\n        }\n    }\n}\n\n// Convert an AST assignment to an IR assignment. (This will always succeed.)\nimpl TryLift<()> for ast::AssignOp {\n    type IR = ir::AssignOp;\n    type Error = IRError;\n\n    fn try_lift(&self, _: (), _: &mut ReportCollection) -> IRResult<Self::IR> {\n        match self {\n            ast::AssignOp::AssignSignal => Ok(ir::AssignOp::AssignSignal),\n            ast::AssignOp::AssignVar => Ok(ir::AssignOp::AssignLocalOrComponent),\n            ast::AssignOp::AssignConstraintSignal => Ok(ir::AssignOp::AssignConstraintSignal),\n        }\n    }\n}\n\n// Convert an AST opcode to an IR opcode. (This will always succeed.)\nimpl TryLift<()> for ast::ExpressionPrefixOpcode {\n    type IR = ir::ExpressionPrefixOpcode;\n    type Error = IRError;\n\n    fn try_lift(&self, _: (), _: &mut ReportCollection) -> IRResult<Self::IR> {\n        match self {\n            ast::ExpressionPrefixOpcode::Sub => Ok(ir::ExpressionPrefixOpcode::Sub),\n            ast::ExpressionPrefixOpcode::BoolNot => Ok(ir::ExpressionPrefixOpcode::BoolNot),\n            ast::ExpressionPrefixOpcode::Complement => Ok(ir::ExpressionPrefixOpcode::Complement),\n        }\n    }\n}\n\n// Convert an AST opcode to an IR opcode. (This will always succeed.)\nimpl TryLift<()> for ast::ExpressionInfixOpcode {\n    type IR = ir::ExpressionInfixOpcode;\n    type Error = IRError;\n\n    fn try_lift(&self, _: (), _: &mut ReportCollection) -> IRResult<Self::IR> {\n        match self {\n            ast::ExpressionInfixOpcode::Mul => Ok(ir::ExpressionInfixOpcode::Mul),\n            ast::ExpressionInfixOpcode::Div => Ok(ir::ExpressionInfixOpcode::Div),\n            ast::ExpressionInfixOpcode::Add => Ok(ir::ExpressionInfixOpcode::Add),\n            ast::ExpressionInfixOpcode::Sub => Ok(ir::ExpressionInfixOpcode::Sub),\n            ast::ExpressionInfixOpcode::Pow => Ok(ir::ExpressionInfixOpcode::Pow),\n            ast::ExpressionInfixOpcode::IntDiv => Ok(ir::ExpressionInfixOpcode::IntDiv),\n            ast::ExpressionInfixOpcode::Mod => Ok(ir::ExpressionInfixOpcode::Mod),\n            ast::ExpressionInfixOpcode::ShiftL => Ok(ir::ExpressionInfixOpcode::ShiftL),\n            ast::ExpressionInfixOpcode::ShiftR => Ok(ir::ExpressionInfixOpcode::ShiftR),\n            ast::ExpressionInfixOpcode::LesserEq => Ok(ir::ExpressionInfixOpcode::LesserEq),\n            ast::ExpressionInfixOpcode::GreaterEq => Ok(ir::ExpressionInfixOpcode::GreaterEq),\n            ast::ExpressionInfixOpcode::Lesser => Ok(ir::ExpressionInfixOpcode::Lesser),\n            ast::ExpressionInfixOpcode::Greater => Ok(ir::ExpressionInfixOpcode::Greater),\n            ast::ExpressionInfixOpcode::Eq => Ok(ir::ExpressionInfixOpcode::Eq),\n            ast::ExpressionInfixOpcode::NotEq => Ok(ir::ExpressionInfixOpcode::NotEq),\n            ast::ExpressionInfixOpcode::BoolOr => Ok(ir::ExpressionInfixOpcode::BoolOr),\n            ast::ExpressionInfixOpcode::BoolAnd => Ok(ir::ExpressionInfixOpcode::BoolAnd),\n            ast::ExpressionInfixOpcode::BitOr => Ok(ir::ExpressionInfixOpcode::BitOr),\n            ast::ExpressionInfixOpcode::BitAnd => Ok(ir::ExpressionInfixOpcode::BitAnd),\n            ast::ExpressionInfixOpcode::BitXor => Ok(ir::ExpressionInfixOpcode::BitXor),\n        }\n    }\n}\n\nimpl TryLift<()> for LogArgument {\n    type IR = ir::LogArgument;\n    type Error = IRError;\n\n    fn try_lift(&self, _: (), reports: &mut ReportCollection) -> IRResult<Self::IR> {\n        match self {\n            ast::LogArgument::LogStr(message) => Ok(ir::LogArgument::String(message.clone())),\n            ast::LogArgument::LogExp(value) => {\n                Ok(ir::LogArgument::Expr(Box::new(value.try_lift((), reports)?)))\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use proptest::prelude::*;\n\n    use crate::report::ReportCollection;\n\n    use super::*;\n\n    proptest! {\n        #[test]\n        fn variable_name_from_string(name in \"[$_]*[a-zA-Z][a-zA-Z$_0-9]*\") {\n            let meta = ast::Meta::new(0, 1);\n            let mut reports = ReportCollection::new();\n\n            let var = name.try_lift(&meta, &mut reports).unwrap();\n            assert!(var.suffix().is_none());\n            assert!(var.version().is_none());\n            assert!(reports.is_empty());\n        }\n\n        #[test]\n        fn variable_name_with_suffix_from_string(name in \"[$_]*[a-zA-Z][a-zA-Z$_0-9]*\\\\.[a-zA-Z$_0-9]*\") {\n            let meta = ast::Meta::new(0, 1);\n            let mut reports = ReportCollection::new();\n\n            let var = name.try_lift(&meta, &mut reports).unwrap();\n            assert!(var.suffix().is_some());\n            assert!(var.version().is_none());\n            assert!(reports.is_empty());\n        }\n\n        #[test]\n        fn variable_name_from_invalid_string(name in \"[$_]*[a-zA-Z][a-zA-Z$_0-9]*\\\\.[a-zA-Z$_0-9]*\\\\.[a-zA-Z$_0-9]*\") {\n            let meta = ast::Meta::new(0, 1);\n            let mut reports = ReportCollection::new();\n\n            let result = name.try_lift(&meta, &mut reports);\n            assert!(result.is_err());\n            assert!(reports.is_empty());\n        }\n    }\n}\n"
  },
  {
    "path": "program_structure/src/intermediate_representation/mod.rs",
    "content": "pub mod declarations;\npub mod degree_meta;\npub mod errors;\npub mod type_meta;\npub mod value_meta;\npub mod variable_meta;\n\nmod expression_impl;\nmod ir;\npub mod lifting;\nmod statement_impl;\n\npub use ir::*;\n"
  },
  {
    "path": "program_structure/src/intermediate_representation/statement_impl.rs",
    "content": "use log::trace;\nuse std::fmt;\n\nuse super::declarations::Declarations;\nuse super::ir::*;\nuse super::degree_meta::{Degree, DegreeEnvironment, DegreeMeta};\nuse super::type_meta::TypeMeta;\nuse super::value_meta::{ValueEnvironment, ValueMeta};\nuse super::variable_meta::{VariableMeta, VariableUse, VariableUses};\n\nimpl Statement {\n    #[must_use]\n    pub fn meta(&self) -> &Meta {\n        use Statement::*;\n        match self {\n            Declaration { meta, .. }\n            | IfThenElse { meta, .. }\n            | Return { meta, .. }\n            | Substitution { meta, .. }\n            | LogCall { meta, .. }\n            | Assert { meta, .. }\n            | ConstraintEquality { meta, .. } => meta,\n        }\n    }\n\n    #[must_use]\n    pub fn meta_mut(&mut self) -> &mut Meta {\n        use Statement::*;\n        match self {\n            Declaration { meta, .. }\n            | IfThenElse { meta, .. }\n            | Return { meta, .. }\n            | Substitution { meta, .. }\n            | LogCall { meta, .. }\n            | Assert { meta, .. }\n            | ConstraintEquality { meta, .. } => meta,\n        }\n    }\n\n    pub fn propagate_degrees(&mut self, env: &mut DegreeEnvironment) -> bool {\n        let mut result = false;\n\n        use Degree::*;\n        use Statement::*;\n        use VariableType::*;\n        match self {\n            Declaration { names, var_type, .. } => {\n                for name in names.iter() {\n                    // Since we disregard accesses, components are treated as signals.\n                    if matches!(var_type, Signal(_, _) | Component | AnonymousComponent) {\n                        result = result || env.set_degree(name, &Linear.into());\n                    }\n                    env.set_type(name, var_type);\n                }\n                result\n            }\n            Substitution { var, rhe, .. } => {\n                result = result || rhe.propagate_degrees(env);\n                if env.is_local(var) {\n                    if let Some(range) = rhe.degree() {\n                        result = result || env.set_degree(var, range);\n                    }\n                }\n                result\n            }\n            LogCall { args, .. } => {\n                use LogArgument::*;\n                for arg in args {\n                    if let Expr(value) = arg {\n                        result = result || value.propagate_degrees(env);\n                    }\n                }\n                result\n            }\n            IfThenElse { cond, .. } => cond.propagate_degrees(env),\n            Return { value, .. } => value.propagate_degrees(env),\n            Assert { arg, .. } => arg.propagate_degrees(env),\n            ConstraintEquality { lhe, rhe, .. } => {\n                result = result || lhe.propagate_degrees(env);\n                result = result || rhe.propagate_degrees(env);\n                result\n            }\n        }\n    }\n\n    #[must_use]\n    pub fn propagate_values(&mut self, env: &mut ValueEnvironment) -> bool {\n        use Statement::*;\n        use Expression::*;\n        match self {\n            Declaration { dimensions, .. } => {\n                let mut result = false;\n                for size in dimensions {\n                    result = result || size.propagate_values(env);\n                }\n                result\n            }\n            Substitution { meta, var, rhe, .. } => {\n                let mut result = rhe.propagate_values(env);\n\n                // TODO: Handle array values.\n                if !matches!(rhe, Update { .. }) {\n                    if let Some(value) = rhe.value() {\n                        env.add_variable(var, value);\n                        result = result || meta.value_knowledge_mut().set_reduces_to(value.clone());\n                    }\n                }\n                trace!(\"Substitution returned {result}\");\n                result\n            }\n            LogCall { args, .. } => {\n                let mut result = false;\n                use LogArgument::*;\n                for arg in args {\n                    if let Expr(value) = arg {\n                        result = result || value.propagate_values(env);\n                    }\n                }\n                result\n            }\n            IfThenElse { cond, .. } => cond.propagate_values(env),\n            Return { value, .. } => value.propagate_values(env),\n            Assert { arg, .. } => arg.propagate_values(env),\n            ConstraintEquality { lhe, rhe, .. } => {\n                lhe.propagate_values(env) || rhe.propagate_values(env)\n            }\n        }\n    }\n\n    pub fn propagate_types(&mut self, vars: &Declarations) {\n        use Statement::*;\n        match self {\n            Declaration { meta, var_type, dimensions, .. } => {\n                // The metadata tracks the type of the declared variable.\n                meta.type_knowledge_mut().set_variable_type(var_type);\n                for size in dimensions {\n                    size.propagate_types(vars);\n                }\n            }\n            Substitution { meta, var, rhe, .. } => {\n                // The metadata tracks the type of the assigned variable.\n                rhe.propagate_types(vars);\n                if let Some(var_type) = vars.get_type(var) {\n                    meta.type_knowledge_mut().set_variable_type(var_type);\n                }\n            }\n            LogCall { args, .. } => {\n                use LogArgument::*;\n                for arg in args {\n                    if let Expr(value) = arg {\n                        value.propagate_types(vars);\n                    }\n                }\n            }\n            ConstraintEquality { lhe, rhe, .. } => {\n                lhe.propagate_types(vars);\n                rhe.propagate_types(vars);\n            }\n            IfThenElse { cond, .. } => {\n                cond.propagate_types(vars);\n            }\n            Return { value, .. } => {\n                value.propagate_types(vars);\n            }\n            Assert { arg, .. } => {\n                arg.propagate_types(vars);\n            }\n        }\n    }\n}\n\nimpl fmt::Debug for Statement {\n    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {\n        use Statement::*;\n        match self {\n            Declaration { names, var_type, dimensions, .. } => {\n                write!(f, \"{var_type} \")?;\n                let mut first = true;\n                for name in names {\n                    if first {\n                        first = false;\n                    } else {\n                        write!(f, \", \")?;\n                    }\n                    write!(f, \"{name:?}\")?;\n                    for size in dimensions {\n                        write!(f, \"[{size:?}]\")?;\n                    }\n                }\n                Ok(())\n            }\n            Substitution { var, op, rhe, .. } => write!(f, \"{var:?} {op} {rhe:?}\"),\n            ConstraintEquality { lhe, rhe, .. } => write!(f, \"{lhe:?} === {rhe:?}\"),\n            IfThenElse { cond, true_index, false_index, .. } => match false_index {\n                Some(false_index) => write!(f, \"if {cond:?} then {true_index} else {false_index}\"),\n                None => write!(f, \"if {cond:?} then {true_index}\"),\n            },\n            Return { value, .. } => write!(f, \"return {value:?}\"),\n            Assert { arg, .. } => write!(f, \"assert({arg:?})\"),\n            LogCall { args, .. } => write!(f, \"log({:?})\", vec_to_debug(args, \", \")),\n        }\n    }\n}\n\nimpl fmt::Display for Statement {\n    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {\n        use Statement::*;\n        match self {\n            Declaration { names, var_type, dimensions, .. } => {\n                // We rewrite declarations of multiple SSA variables as a single\n                // declaration of the original variable.\n                write!(f, \"{var_type} {}\", names.first())?;\n                for size in dimensions {\n                    write!(f, \"[{size}]\")?;\n                }\n                Ok(())\n            }\n            Substitution { var, op, rhe, .. } => {\n                match rhe {\n                    // We rewrite `Update` expressions of arrays/component signals.\n                    Expression::Update { access, rhe, .. } => {\n                        write!(f, \"{var}\")?;\n                        for access in access {\n                            write!(f, \"{access}\")?;\n                        }\n                        write!(f, \" {op} {rhe}\")\n                    }\n                    // This is an ordinary assignment.\n                    _ => write!(f, \"{var} {op} {rhe}\"),\n                }\n            }\n            ConstraintEquality { lhe, rhe, .. } => write!(f, \"{lhe} === {rhe}\"),\n            IfThenElse { cond, .. } => write!(f, \"if {cond}\"),\n            Return { value, .. } => write!(f, \"return {value}\"),\n            Assert { arg, .. } => write!(f, \"assert({arg})\"),\n            LogCall { args, .. } => write!(f, \"log({})\", vec_to_display(args, \", \")),\n        }\n    }\n}\n\nimpl fmt::Display for AssignOp {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        use AssignOp::*;\n        match self {\n            AssignSignal => write!(f, \"<--\"),\n            AssignConstraintSignal => write!(f, \"<==\"),\n            AssignLocalOrComponent => write!(f, \"=\"),\n        }\n    }\n}\n\nimpl fmt::Display for LogArgument {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        use LogArgument::*;\n        match self {\n            String(message) => write!(f, \"{message}\"),\n            Expr(value) => write!(f, \"{value}\"),\n        }\n    }\n}\n\nimpl fmt::Debug for LogArgument {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        use LogArgument::*;\n        match self {\n            String(message) => write!(f, \"{message:?}\"),\n            Expr(value) => write!(f, \"{value:?}\"),\n        }\n    }\n}\n\nimpl VariableMeta for Statement {\n    fn cache_variable_use(&mut self) {\n        let mut locals_read = VariableUses::new();\n        let mut locals_written = VariableUses::new();\n        let mut signals_read = VariableUses::new();\n        let mut signals_written = VariableUses::new();\n        let mut components_read = VariableUses::new();\n        let mut components_written = VariableUses::new();\n\n        use Statement::*;\n        use Expression::*;\n        match self {\n            Declaration { dimensions, .. } => {\n                for size in dimensions {\n                    size.cache_variable_use();\n                    locals_read.extend(size.locals_read().clone());\n                    signals_read.extend(size.signals_read().clone());\n                    components_read.extend(size.components_read().clone());\n                }\n            }\n            Substitution { meta, var, op, rhe } => {\n                rhe.cache_variable_use();\n                locals_read.extend(rhe.locals_read().clone());\n                signals_read.extend(rhe.signals_read().clone());\n                components_read.extend(rhe.components_read().clone());\n\n                let access = match rhe {\n                    Update { access, .. } => access.clone(),\n                    _ => Vec::new(),\n                };\n                match meta.type_knowledge().variable_type() {\n                    Some(VariableType::Local) => {\n                        trace!(\"adding `{var:?}` to local variables written\");\n                        locals_written.insert(VariableUse::new(meta, var, &access));\n                    }\n                    Some(VariableType::Signal(_, _)) => {\n                        trace!(\"adding `{var:?}` to signals written\");\n                        signals_written.insert(VariableUse::new(meta, var, &access));\n                        if matches!(op, AssignOp::AssignConstraintSignal) {\n                            // If this is a signal constraint assignment, we\n                            // consider the assigned signal to be read as well.\n                            trace!(\"adding `{var:?}` to signals read\");\n                            signals_read.insert(VariableUse::new(meta, var, &access));\n                        }\n                    }\n                    Some(VariableType::Component | VariableType::AnonymousComponent) => {\n                        trace!(\"adding `{var:?}` to components written\");\n                        components_written.insert(VariableUse::new(meta, var, &access));\n                    }\n                    None => {\n                        trace!(\"variable `{var:?}` of unknown type written\");\n                    }\n                }\n            }\n            LogCall { args, .. } => {\n                use LogArgument::*;\n                for arg in args {\n                    if let Expr(value) = arg {\n                        value.cache_variable_use();\n                        locals_read.extend(value.locals_read().clone());\n                        signals_read.extend(value.signals_read().clone());\n                        components_read.extend(value.components_read().clone());\n                    }\n                }\n            }\n            IfThenElse { cond, .. } => {\n                cond.cache_variable_use();\n                locals_read.extend(cond.locals_read().clone());\n                signals_read.extend(cond.signals_read().clone());\n                components_read.extend(cond.components_read().clone());\n            }\n            Return { value, .. } => {\n                value.cache_variable_use();\n                locals_read.extend(value.locals_read().clone());\n                signals_read.extend(value.signals_read().clone());\n                components_read.extend(value.components_read().clone());\n            }\n            Assert { arg, .. } => {\n                arg.cache_variable_use();\n                locals_read.extend(arg.locals_read().clone());\n                signals_read.extend(arg.signals_read().clone());\n                components_read.extend(arg.components_read().clone());\n            }\n            ConstraintEquality { lhe, rhe, .. } => {\n                lhe.cache_variable_use();\n                rhe.cache_variable_use();\n                locals_read.extend(lhe.locals_read().iter().cloned());\n                locals_read.extend(rhe.locals_read().iter().cloned());\n                signals_read.extend(lhe.signals_read().iter().cloned());\n                signals_read.extend(rhe.signals_read().iter().cloned());\n                components_read.extend(lhe.components_read().iter().cloned());\n                components_read.extend(rhe.components_read().iter().cloned());\n            }\n        }\n        self.meta_mut()\n            .variable_knowledge_mut()\n            .set_locals_read(&locals_read)\n            .set_locals_written(&locals_written)\n            .set_signals_read(&signals_read)\n            .set_signals_written(&signals_written)\n            .set_components_read(&components_read)\n            .set_components_written(&components_written);\n    }\n\n    fn locals_read(&self) -> &VariableUses {\n        self.meta().variable_knowledge().locals_read()\n    }\n\n    fn locals_written(&self) -> &VariableUses {\n        self.meta().variable_knowledge().locals_written()\n    }\n\n    fn signals_read(&self) -> &VariableUses {\n        self.meta().variable_knowledge().signals_read()\n    }\n\n    fn signals_written(&self) -> &VariableUses {\n        self.meta().variable_knowledge().signals_written()\n    }\n\n    fn components_read(&self) -> &VariableUses {\n        self.meta().variable_knowledge().components_read()\n    }\n\n    fn components_written(&self) -> &VariableUses {\n        self.meta().variable_knowledge().components_written()\n    }\n}\n\n#[must_use]\nfn vec_to_debug<T: fmt::Debug>(elems: &[T], sep: &str) -> String {\n    elems.iter().map(|elem| format!(\"{elem:?}\")).collect::<Vec<String>>().join(sep)\n}\n\n#[must_use]\nfn vec_to_display<T: fmt::Display>(elems: &[T], sep: &str) -> String {\n    elems.iter().map(|elem| format!(\"{elem}\")).collect::<Vec<String>>().join(sep)\n}\n"
  },
  {
    "path": "program_structure/src/intermediate_representation/type_meta.rs",
    "content": "use super::declarations::Declarations;\nuse super::ir::VariableType;\n\npub trait TypeMeta {\n    /// Propagate variable types to variable child nodes.\n    fn propagate_types(&mut self, vars: &Declarations);\n\n    /// Returns true if the node is a local variable.\n    #[must_use]\n    fn is_local(&self) -> bool;\n\n    /// Returns true if the node is a signal.\n    #[must_use]\n    fn is_signal(&self) -> bool;\n\n    /// Returns true if the node is a component.\n    #[must_use]\n    fn is_component(&self) -> bool;\n\n    /// For declared variables, this returns the type. For undeclared variables\n    /// and other expression nodes this returns `None`.\n    #[must_use]\n    fn variable_type(&self) -> Option<&VariableType>;\n}\n\n#[derive(Default, Clone)]\npub struct TypeKnowledge {\n    var_type: Option<VariableType>,\n}\n\nimpl TypeKnowledge {\n    #[must_use]\n    pub fn new() -> TypeKnowledge {\n        TypeKnowledge::default()\n    }\n\n    // Sets the variable type of a node representing a variable.\n    pub fn set_variable_type(&mut self, var_type: &VariableType) {\n        self.var_type = Some(var_type.clone());\n    }\n\n    /// For declared variables, this returns the type. For undeclared variables\n    /// and other expression nodes this returns `None`.\n    #[must_use]\n    pub fn variable_type(&self) -> Option<&VariableType> {\n        self.var_type.as_ref()\n    }\n\n    /// Returns true if the node is a local variable.\n    #[must_use]\n    pub fn is_local(&self) -> bool {\n        matches!(self.var_type, Some(VariableType::Local))\n    }\n\n    /// Returns true if the node is a signal.\n    #[must_use]\n    pub fn is_signal(&self) -> bool {\n        matches!(self.var_type, Some(VariableType::Signal(_, _)))\n    }\n\n    /// Returns true if the node is a (possibly anonymous) component.\n    #[must_use]\n    pub fn is_component(&self) -> bool {\n        matches!(self.var_type, Some(VariableType::Component | VariableType::AnonymousComponent))\n    }\n}\n"
  },
  {
    "path": "program_structure/src/intermediate_representation/value_meta.rs",
    "content": "use num_bigint::BigInt;\nuse std::collections::HashMap;\nuse std::fmt;\n\nuse crate::constants::UsefulConstants;\n\nuse super::ir::VariableName;\n\n#[derive(Clone)]\npub struct ValueEnvironment {\n    constants: UsefulConstants,\n    reduces_to: HashMap<VariableName, ValueReduction>,\n}\n\nimpl ValueEnvironment {\n    pub fn new(constants: &UsefulConstants) -> ValueEnvironment {\n        ValueEnvironment { constants: constants.clone(), reduces_to: HashMap::new() }\n    }\n\n    /// Set the value of the given variable. Returns `true` on first update.\n    ///\n    /// # Panics\n    ///\n    /// This function panics if the caller attempts to set two different values\n    /// for the same variable.\n    pub fn add_variable(&mut self, name: &VariableName, value: &ValueReduction) -> bool {\n        if let Some(previous) = self.reduces_to.insert(name.clone(), value.clone()) {\n            assert_eq!(previous, *value);\n            false\n        } else {\n            true\n        }\n    }\n\n    #[must_use]\n    pub fn get_variable(&self, name: &VariableName) -> Option<&ValueReduction> {\n        self.reduces_to.get(name)\n    }\n\n    /// Returns the prime used.\n    pub fn prime(&self) -> &BigInt {\n        self.constants.prime()\n    }\n}\n\npub trait ValueMeta {\n    /// Propagate variable values defined by the environment to each sub-node.\n    /// The method returns true if the node (or a sub-node) was updated.\n    fn propagate_values(&mut self, env: &mut ValueEnvironment) -> bool;\n\n    /// Returns true if the node reduces to a constant value.\n    #[must_use]\n    fn is_constant(&self) -> bool;\n\n    /// Returns true if the node reduces to a boolean value.\n    #[must_use]\n    fn is_boolean(&self) -> bool;\n\n    /// Returns true if the node reduces to a field element.\n    #[must_use]\n    fn is_field_element(&self) -> bool;\n\n    /// Returns the value if the node reduces to a constant, and None otherwise.\n    #[must_use]\n    fn value(&self) -> Option<&ValueReduction>;\n}\n\n#[derive(Debug, Clone, Hash, PartialEq, Eq)]\npub enum ValueReduction {\n    Boolean { value: bool },\n    FieldElement { value: BigInt },\n}\n\nimpl fmt::Display for ValueReduction {\n    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {\n        use ValueReduction::*;\n        match self {\n            Boolean { value } => write!(f, \"{value}\"),\n            FieldElement { value } => write!(f, \"{value}\"),\n        }\n    }\n}\n\n#[derive(Default, Clone)]\npub struct ValueKnowledge {\n    reduces_to: Option<ValueReduction>,\n}\n\nimpl ValueKnowledge {\n    #[must_use]\n    pub fn new() -> ValueKnowledge {\n        ValueKnowledge::default()\n    }\n\n    /// Sets the value of the node. Returns `true` on the first update.\n    #[must_use]\n    pub fn set_reduces_to(&mut self, reduces_to: ValueReduction) -> bool {\n        let result = self.reduces_to.is_none();\n        self.reduces_to = Some(reduces_to);\n        result\n    }\n\n    /// Gets the value of the node. Returns `None` if the value is unknown.\n    #[must_use]\n    pub fn get_reduces_to(&self) -> Option<&ValueReduction> {\n        self.reduces_to.as_ref()\n    }\n\n    /// Returns `true` if the value of the node is known.\n    #[must_use]\n    pub fn is_constant(&self) -> bool {\n        self.reduces_to.is_some()\n    }\n\n    /// Returns `true` if the value of the node is a boolean.\n    #[must_use]\n    pub fn is_boolean(&self) -> bool {\n        use ValueReduction::*;\n        matches!(self.reduces_to, Some(Boolean { .. }))\n    }\n\n    /// Returns `true` if the value of the node is a field element.\n    #[must_use]\n    pub fn is_field_element(&self) -> bool {\n        use ValueReduction::*;\n        matches!(self.reduces_to, Some(FieldElement { .. }))\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use num_bigint::BigInt;\n\n    use crate::ir::value_meta::ValueReduction;\n\n    use super::ValueKnowledge;\n\n    #[test]\n    fn test_value_knowledge() {\n        let mut value = ValueKnowledge::new();\n        assert!(matches!(value.get_reduces_to(), None));\n\n        let number = ValueReduction::FieldElement { value: BigInt::from(1) };\n        assert!(value.set_reduces_to(number));\n        assert!(matches!(value.get_reduces_to(), Some(ValueReduction::FieldElement { .. })));\n        assert!(value.is_field_element());\n        assert!(!value.is_boolean());\n\n        let boolean = ValueReduction::Boolean { value: true };\n        assert!(!value.set_reduces_to(boolean));\n        assert!(matches!(value.get_reduces_to(), Some(ValueReduction::Boolean { .. })));\n        assert!(!value.is_field_element());\n        assert!(value.is_boolean());\n    }\n}\n"
  },
  {
    "path": "program_structure/src/intermediate_representation/variable_meta.rs",
    "content": "use std::fmt;\nuse std::collections::HashSet;\n\nuse super::ir::{AccessType, Meta, VariableName};\n\n/// A variable use (a variable, component or signal read or write).\n#[derive(Clone, Hash, PartialEq, Eq)]\npub struct VariableUse {\n    meta: Meta,\n    name: VariableName,\n    access: Vec<AccessType>,\n}\n\nimpl VariableUse {\n    pub fn new(meta: &Meta, name: &VariableName, access: &[AccessType]) -> VariableUse {\n        VariableUse { meta: meta.clone(), name: name.clone(), access: access.to_owned() }\n    }\n\n    pub fn meta(&self) -> &Meta {\n        &self.meta\n    }\n\n    pub fn name(&self) -> &VariableName {\n        &self.name\n    }\n\n    pub fn access(&self) -> &Vec<AccessType> {\n        &self.access\n    }\n}\n\nimpl fmt::Display for VariableUse {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{}\", self.name)?;\n        for access in &self.access {\n            write!(f, \"{access}\")?;\n        }\n        Ok(())\n    }\n}\n\npub type VariableUses = HashSet<VariableUse>;\n\npub trait VariableMeta {\n    /// Compute variables read/written by the node. Must be called before either\n    /// of the getters are called. To avoid interior mutability this needs to be\n    /// called again whenever the node is mutated in a way that may invalidate\n    /// the cached variable use.\n    fn cache_variable_use(&mut self);\n\n    /// Get the set of variables read by the IR node.\n    #[must_use]\n    fn locals_read(&self) -> &VariableUses;\n\n    /// Get the set of variables written by the IR node.\n    #[must_use]\n    fn locals_written(&self) -> &VariableUses;\n\n    /// Get the set of signals read by the IR node. Note that this does not\n    /// include signals belonging to sub-components.\n    #[must_use]\n    fn signals_read(&self) -> &VariableUses;\n\n    /// Get the set of signals written by the IR node. Note that this does not\n    /// include signals belonging to sub-components.\n    #[must_use]\n    fn signals_written(&self) -> &VariableUses;\n\n    /// Get the set of components read by the IR node. Note that a component\n    /// read is typically a signal read for a signal exported by the component.\n    #[must_use]\n    fn components_read(&self) -> &VariableUses;\n\n    /// Get the set of components written by the IR node. Note that a component\n    /// write may either be a component initialization, or a signal write for a\n    /// signal exported by the component.\n    #[must_use]\n    fn components_written(&self) -> &VariableUses;\n\n    /// Get the set of variables read by the IR node. Note that this is simply\n    /// the union of all locals, signals, and components read by the node.\n    #[must_use]\n    fn variables_read<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VariableUse> + 'a> {\n        let locals_read = self.locals_read().iter();\n        let signals_read = self.signals_read().iter();\n        let components_read = self.components_read().iter();\n        Box::new(locals_read.chain(signals_read).chain(components_read))\n    }\n\n    /// Get the set of variables written by the IR node. Note that this is\n    /// simply the union of all locals, signals, and components written.\n    #[must_use]\n    fn variables_written<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VariableUse> + 'a> {\n        let locals_written = self.locals_written().iter();\n        let signals_written = self.signals_written().iter();\n        let components_written = self.components_written().iter();\n        Box::new(locals_written.chain(signals_written).chain(components_written))\n    }\n\n    /// Get the set of variables either read or written by the IR node.\n    #[must_use]\n    fn variables_used<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VariableUse> + 'a> {\n        Box::new(self.variables_read().chain(self.variables_written()))\n    }\n}\n\n#[derive(Default, Clone)]\npub struct VariableKnowledge {\n    locals_read: Option<VariableUses>,\n    locals_written: Option<VariableUses>,\n    signals_read: Option<VariableUses>,\n    signals_written: Option<VariableUses>,\n    components_read: Option<VariableUses>,\n    components_written: Option<VariableUses>,\n}\n\nimpl VariableKnowledge {\n    #[must_use]\n    pub fn new() -> VariableKnowledge {\n        VariableKnowledge::default()\n    }\n\n    pub fn set_locals_read(&mut self, uses: &VariableUses) -> &mut VariableKnowledge {\n        self.locals_read = Some(uses.clone());\n        self\n    }\n\n    pub fn set_locals_written(&mut self, uses: &VariableUses) -> &mut VariableKnowledge {\n        self.locals_written = Some(uses.clone());\n        self\n    }\n\n    pub fn set_signals_read(&mut self, uses: &VariableUses) -> &mut VariableKnowledge {\n        self.signals_read = Some(uses.clone());\n        self\n    }\n\n    pub fn set_signals_written(&mut self, uses: &VariableUses) -> &mut VariableKnowledge {\n        self.signals_written = Some(uses.clone());\n        self\n    }\n\n    pub fn set_components_read(&mut self, uses: &VariableUses) -> &mut VariableKnowledge {\n        self.components_read = Some(uses.clone());\n        self\n    }\n\n    pub fn set_components_written(&mut self, uses: &VariableUses) -> &mut VariableKnowledge {\n        self.components_written = Some(uses.clone());\n        self\n    }\n\n    #[must_use]\n    pub fn locals_read(&self) -> &VariableUses {\n        self.locals_read.as_ref().expect(\"variable knowledge must be initialized before it is read\")\n    }\n\n    #[must_use]\n    pub fn locals_written(&self) -> &VariableUses {\n        self.locals_written\n            .as_ref()\n            .expect(\"variable knowledge must be initialized before it is read\")\n    }\n\n    #[must_use]\n    pub fn signals_read(&self) -> &VariableUses {\n        self.signals_read\n            .as_ref()\n            .expect(\"variable knowledge must be initialized before it is read\")\n    }\n\n    #[must_use]\n    pub fn signals_written(&self) -> &VariableUses {\n        self.signals_written\n            .as_ref()\n            .expect(\"variable knowledge must be initialized before it is read\")\n    }\n\n    #[must_use]\n    pub fn components_read(&self) -> &VariableUses {\n        self.components_read\n            .as_ref()\n            .expect(\"variable knowledge must be initialized before it is read\")\n    }\n\n    #[must_use]\n    pub fn components_written(&self) -> &VariableUses {\n        self.components_written\n            .as_ref()\n            .expect(\"variable knowledge must be initialized before it is read\")\n    }\n\n    #[must_use]\n    pub fn variables_read<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VariableUse> + 'a> {\n        let locals_read = self.locals_read().iter();\n        let signals_read = self.signals_read().iter();\n        let components_read = self.components_read().iter();\n        Box::new(locals_read.chain(signals_read).chain(components_read))\n    }\n\n    #[must_use]\n    pub fn variables_written<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VariableUse> + 'a> {\n        let locals_written = self.locals_written().iter();\n        let signals_written = self.signals_written().iter();\n        let components_written = self.components_written().iter();\n        Box::new(locals_written.chain(signals_written).chain(components_written))\n    }\n\n    #[must_use]\n    pub fn variables_used<'a>(&'a self) -> Box<dyn Iterator<Item = &'a VariableUse> + 'a> {\n        let variables_read = self.variables_read();\n        let variables_written = self.variables_written();\n        Box::new(variables_read.chain(variables_written))\n    }\n}\n"
  },
  {
    "path": "program_structure/src/lib.rs",
    "content": "extern crate num_bigint_dig as num_bigint;\nextern crate num_traits;\n\npub mod abstract_syntax_tree;\npub mod control_flow_graph;\npub mod intermediate_representation;\npub mod program_library;\npub mod static_single_assignment;\npub mod utils;\n\n// Library interface\npub use abstract_syntax_tree::*;\npub use control_flow_graph as cfg;\npub use intermediate_representation as ir;\npub use program_library::*;\npub use static_single_assignment as ssa;\npub use utils::*;\n"
  },
  {
    "path": "program_structure/src/program_library/file_definition.rs",
    "content": "use codespan_reporting::files::{Files, SimpleFiles};\nuse std::{ops::Range, collections::HashSet};\n\npub type FileSource = String;\npub type FilePath = String;\npub type FileID = usize;\npub type FileLocation = Range<usize>;\ntype FileStorage = SimpleFiles<FilePath, FileSource>;\n\n#[derive(Clone)]\npub struct FileLibrary {\n    files: FileStorage,\n    user_inputs: HashSet<FileID>,\n}\n\nimpl Default for FileLibrary {\n    fn default() -> Self {\n        FileLibrary { files: FileStorage::new(), user_inputs: HashSet::new() }\n    }\n}\n\nimpl FileLibrary {\n    pub fn new() -> FileLibrary {\n        FileLibrary::default()\n    }\n    pub fn add_file(\n        &mut self,\n        file_name: FilePath,\n        file_source: FileSource,\n        is_user_input: bool,\n    ) -> FileID {\n        let file_id = self.get_mut_files().add(file_name, file_source);\n        if is_user_input {\n            self.user_inputs.insert(file_id);\n        }\n        file_id\n    }\n\n    pub fn get_line(&self, start: usize, file_id: FileID) -> Option<usize> {\n        self.files.line_index(file_id, start).map(|lines| lines + 1).ok()\n    }\n\n    pub fn to_storage(&self) -> &FileStorage {\n        self.get_files()\n    }\n\n    pub fn user_inputs(&self) -> &HashSet<FileID> {\n        &self.user_inputs\n    }\n\n    pub fn is_user_input(&self, file_id: FileID) -> bool {\n        self.user_inputs.contains(&file_id)\n    }\n\n    fn get_files(&self) -> &FileStorage {\n        &self.files\n    }\n\n    fn get_mut_files(&mut self) -> &mut FileStorage {\n        &mut self.files\n    }\n}\n\npub fn generate_file_location(start: usize, end: usize) -> FileLocation {\n    start..end\n}\n"
  },
  {
    "path": "program_structure/src/program_library/function_data.rs",
    "content": "use super::ast::{FillMeta, Statement};\nuse super::file_definition::FileID;\nuse crate::file_definition::FileLocation;\nuse std::collections::HashMap;\n\npub type FunctionInfo = HashMap<String, FunctionData>;\n\n#[derive(Clone)]\npub struct FunctionData {\n    name: String,\n    file_id: FileID,\n    num_of_params: usize,\n    name_of_params: Vec<String>,\n    param_location: FileLocation,\n    body: Statement,\n}\n\nimpl FunctionData {\n    pub fn new(\n        name: String,\n        file_id: FileID,\n        mut body: Statement,\n        num_of_params: usize,\n        name_of_params: Vec<String>,\n        param_location: FileLocation,\n        elem_id: &mut usize,\n    ) -> FunctionData {\n        body.fill(file_id, elem_id);\n        FunctionData { name, file_id, body, name_of_params, param_location, num_of_params }\n    }\n    pub fn get_file_id(&self) -> FileID {\n        self.file_id\n    }\n    pub fn get_body(&self) -> &Statement {\n        &self.body\n    }\n    pub fn get_body_as_vec(&self) -> &Vec<Statement> {\n        match &self.body {\n            Statement::Block { stmts, .. } => stmts,\n            _ => panic!(\"Function body should be a block\"),\n        }\n    }\n    pub fn get_mut_body(&mut self) -> &mut Statement {\n        &mut self.body\n    }\n    pub fn replace_body(&mut self, new: Statement) -> Statement {\n        std::mem::replace(&mut self.body, new)\n    }\n    pub fn get_mut_body_as_vec(&mut self) -> &mut Vec<Statement> {\n        match &mut self.body {\n            Statement::Block { stmts, .. } => stmts,\n            _ => panic!(\"Function body should be a block\"),\n        }\n    }\n    pub fn get_param_location(&self) -> FileLocation {\n        self.param_location.clone()\n    }\n    pub fn get_num_of_params(&self) -> usize {\n        self.num_of_params\n    }\n    pub fn get_name_of_params(&self) -> &Vec<String> {\n        &self.name_of_params\n    }\n    pub fn get_name(&self) -> &str {\n        &self.name\n    }\n}\n"
  },
  {
    "path": "program_structure/src/program_library/mod.rs",
    "content": "use super::ast;\npub mod report_code;\npub mod report;\npub mod file_definition;\npub mod function_data;\npub mod program_archive;\npub mod program_merger;\npub mod template_data;\npub mod template_library;\n"
  },
  {
    "path": "program_structure/src/program_library/program_archive.rs",
    "content": "use super::ast::{Definition, Expression, MainComponent};\nuse super::file_definition::{FileID, FileLibrary};\nuse super::function_data::{FunctionData, FunctionInfo};\nuse super::program_merger::Merger;\nuse super::template_data::{TemplateData, TemplateInfo};\nuse crate::abstract_syntax_tree::ast::FillMeta;\nuse crate::report::Report;\nuse std::collections::{HashMap, HashSet};\n\ntype Contents = HashMap<FileID, Vec<Definition>>;\n\n#[derive(Clone)]\npub struct ProgramArchive {\n    pub id_max: usize,\n    pub file_id_main: FileID,\n    pub file_library: FileLibrary,\n    pub functions: FunctionInfo,\n    pub templates: TemplateInfo,\n    pub function_keys: HashSet<String>,\n    pub template_keys: HashSet<String>,\n    pub public_inputs: Vec<String>,\n    pub initial_template_call: Expression,\n    pub custom_gates: bool,\n}\nimpl ProgramArchive {\n    pub fn new(\n        file_library: FileLibrary,\n        file_id_main: FileID,\n        main_component: &MainComponent,\n        program_contents: &Contents,\n        custom_gates: bool,\n    ) -> Result<ProgramArchive, (FileLibrary, Vec<Report>)> {\n        let mut merger = Merger::new();\n        let mut reports = vec![];\n        for (file_id, definitions) in program_contents {\n            if let Err(mut errs) = merger.add_definitions(*file_id, definitions) {\n                reports.append(&mut errs);\n            }\n        }\n        let (mut fresh_id, functions, templates) = merger.decompose();\n        let mut function_keys = HashSet::new();\n        let mut template_keys = HashSet::new();\n        for key in functions.keys() {\n            function_keys.insert(key.clone());\n        }\n        for key in templates.keys() {\n            template_keys.insert(key.clone());\n        }\n        let (public_inputs, mut initial_template_call) = main_component.clone();\n        initial_template_call.fill(file_id_main, &mut fresh_id);\n        if reports.is_empty() {\n            Ok(ProgramArchive {\n                id_max: fresh_id,\n                file_id_main,\n                file_library,\n                functions,\n                templates,\n                initial_template_call,\n                function_keys,\n                template_keys,\n                public_inputs,\n                custom_gates,\n            })\n        } else {\n            Err((file_library, reports))\n        }\n    }\n    //file_id_main\n    pub fn get_file_id_main(&self) -> &FileID {\n        &self.file_id_main\n    }\n    //template functions\n    pub fn contains_template(&self, template_name: &str) -> bool {\n        self.templates.contains_key(template_name)\n    }\n    pub fn get_template_data(&self, template_name: &str) -> &TemplateData {\n        assert!(self.contains_template(template_name));\n        self.templates.get(template_name).unwrap()\n    }\n    pub fn get_mut_template_data(&mut self, template_name: &str) -> &mut TemplateData {\n        assert!(self.contains_template(template_name));\n        self.templates.get_mut(template_name).unwrap()\n    }\n    pub fn get_template_names(&self) -> &HashSet<String> {\n        &self.template_keys\n    }\n    pub fn get_templates(&self) -> &TemplateInfo {\n        &self.templates\n    }\n    pub fn get_mut_templates(&mut self) -> &mut TemplateInfo {\n        &mut self.templates\n    }\n\n    pub fn remove_template(&mut self, id: &str) {\n        self.template_keys.remove(id);\n        self.templates.remove(id);\n    }\n\n    //functions functions\n    pub fn contains_function(&self, function_name: &str) -> bool {\n        self.get_functions().contains_key(function_name)\n    }\n    pub fn get_function_data(&self, function_name: &str) -> &FunctionData {\n        assert!(self.contains_function(function_name));\n        self.get_functions().get(function_name).unwrap()\n    }\n    pub fn get_mut_function_data(&mut self, function_name: &str) -> &mut FunctionData {\n        assert!(self.contains_function(function_name));\n        self.functions.get_mut(function_name).unwrap()\n    }\n    pub fn get_function_names(&self) -> &HashSet<String> {\n        &self.function_keys\n    }\n    pub fn get_functions(&self) -> &FunctionInfo {\n        &self.functions\n    }\n    pub fn get_mut_functions(&mut self) -> &mut FunctionInfo {\n        &mut self.functions\n    }\n    pub fn remove_function(&mut self, id: &str) {\n        self.function_keys.remove(id);\n        self.functions.remove(id);\n    }\n\n    //main_component functions\n    pub fn get_public_inputs_main_component(&self) -> &Vec<String> {\n        &self.public_inputs\n    }\n    pub fn main_expression(&self) -> &Expression {\n        &self.initial_template_call\n    }\n    // FileLibrary functions\n    pub fn get_file_library(&self) -> &FileLibrary {\n        &self.file_library\n    }\n}\n"
  },
  {
    "path": "program_structure/src/program_library/program_merger.rs",
    "content": "use super::ast::Definition;\nuse super::report_code::ReportCode;\nuse super::report::Report;\nuse super::file_definition::FileID;\nuse super::function_data::{FunctionData, FunctionInfo};\nuse super::template_data::{TemplateData, TemplateInfo};\n\n#[derive(Default)]\npub struct Merger {\n    fresh_id: usize,\n    function_info: FunctionInfo,\n    template_info: TemplateInfo,\n}\n\nimpl Merger {\n    pub fn new() -> Merger {\n        Merger::default()\n    }\n\n    pub fn add_definitions(\n        &mut self,\n        file_id: FileID,\n        definitions: &Vec<Definition>,\n    ) -> Result<(), Vec<Report>> {\n        let mut reports = vec![];\n        for definition in definitions {\n            let (name, meta) = match definition {\n                Definition::Template {\n                    name,\n                    args,\n                    arg_location,\n                    body,\n                    meta,\n                    parallel,\n                    is_custom_gate,\n                } => {\n                    if self.contains_function(name) || self.contains_template(name) {\n                        (Option::Some(name), meta)\n                    } else {\n                        let new_data = TemplateData::new(\n                            name.clone(),\n                            file_id,\n                            body.clone(),\n                            args.len(),\n                            args.clone(),\n                            arg_location.clone(),\n                            &mut self.fresh_id,\n                            *parallel,\n                            *is_custom_gate,\n                        );\n                        self.get_mut_template_info().insert(name.clone(), new_data);\n                        (Option::None, meta)\n                    }\n                }\n                Definition::Function { name, body, args, arg_location, meta } => {\n                    if self.contains_function(name) || self.contains_template(name) {\n                        (Option::Some(name), meta)\n                    } else {\n                        let new_data = FunctionData::new(\n                            name.clone(),\n                            file_id,\n                            body.clone(),\n                            args.len(),\n                            args.clone(),\n                            arg_location.clone(),\n                            &mut self.fresh_id,\n                        );\n                        self.get_mut_function_info().insert(name.clone(), new_data);\n                        (Option::None, meta)\n                    }\n                }\n            };\n            if let Option::Some(definition_name) = name {\n                let mut report = Report::error(\n                    String::from(\"Duplicated function or template.\"),\n                    ReportCode::SameSymbolDeclaredTwice,\n                );\n                report.add_primary(\n                    meta.file_location(),\n                    file_id,\n                    format!(\"The name `{definition_name}` is already used.\"),\n                );\n                reports.push(report);\n            }\n        }\n        if reports.is_empty() {\n            Ok(())\n        } else {\n            Err(reports)\n        }\n    }\n    pub fn contains_function(&self, function_name: &str) -> bool {\n        self.get_function_info().contains_key(function_name)\n    }\n    fn get_function_info(&self) -> &FunctionInfo {\n        &self.function_info\n    }\n    fn get_mut_function_info(&mut self) -> &mut FunctionInfo {\n        &mut self.function_info\n    }\n\n    pub fn contains_template(&self, template_name: &str) -> bool {\n        self.get_template_info().contains_key(template_name)\n    }\n    fn get_template_info(&self) -> &TemplateInfo {\n        &self.template_info\n    }\n    fn get_mut_template_info(&mut self) -> &mut TemplateInfo {\n        &mut self.template_info\n    }\n\n    pub fn decompose(self) -> (usize, FunctionInfo, TemplateInfo) {\n        (self.fresh_id, self.function_info, self.template_info)\n    }\n}\n"
  },
  {
    "path": "program_structure/src/program_library/report.rs",
    "content": "use anyhow::anyhow;\nuse std::cmp::Ordering;\nuse std::fmt::Display;\nuse std::str::FromStr;\n\nuse codespan_reporting::diagnostic::{Diagnostic, Label};\n\nuse super::report_code::ReportCode;\nuse super::file_definition::{FileID, FileLocation};\n\npub type ReportCollection = Vec<Report>;\npub type DiagnosticCode = String;\npub type ReportLabel = Label<FileID>;\ntype ReportNote = String;\n\n#[derive(Copy, Clone, Debug, PartialEq, Eq)]\npub enum MessageCategory {\n    Error,\n    Warning,\n    Info,\n}\n\n/// Message categories are linearly ordered.\nimpl PartialOrd for MessageCategory {\n    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n        Some(self.cmp(other))\n    }\n}\n\nimpl Ord for MessageCategory {\n    fn cmp(&self, other: &Self) -> Ordering {\n        use MessageCategory::*;\n        match (self, other) {\n            // `Info <= _`\n            (Info, Info) => Ordering::Equal,\n            (Info, Warning) | (Info, Error) => Ordering::Less,\n            // `Warning <= _`\n            (Warning, Warning) => Ordering::Equal,\n            (Warning, Error) => Ordering::Less,\n            // `Error <= _`\n            (Error, Error) => Ordering::Equal,\n            // All other cases are on the form `_ >= _`.\n            _ => Ordering::Greater,\n        }\n    }\n}\n\nimpl Display for MessageCategory {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        use MessageCategory::*;\n        match self {\n            Error => write!(f, \"error\"),\n            Warning => write!(f, \"warning\"),\n            Info => write!(f, \"info\"),\n        }\n    }\n}\n\nimpl FromStr for MessageCategory {\n    type Err = anyhow::Error;\n\n    fn from_str(category: &str) -> Result<MessageCategory, Self::Err> {\n        match category.to_lowercase().as_str() {\n            \"warning\" => Ok(MessageCategory::Warning),\n            \"info\" => Ok(MessageCategory::Info),\n            \"error\" => Ok(MessageCategory::Error),\n            _ => Err(anyhow!(\"unknown level '{category}'\")),\n        }\n    }\n}\n\nimpl MessageCategory {\n    /// Convert message category to Sarif level.\n    pub fn to_level(&self) -> String {\n        use MessageCategory::*;\n        match self {\n            Error => \"error\",\n            Warning => \"warning\",\n            Info => \"note\",\n        }\n        .to_string()\n    }\n}\n\n#[derive(Clone)]\npub struct Report {\n    category: MessageCategory,\n    message: String,\n    primary_file_ids: Vec<FileID>,\n    primary: Vec<ReportLabel>,\n    secondary: Vec<ReportLabel>,\n    notes: Vec<ReportNote>,\n    code: ReportCode,\n}\n\nimpl Report {\n    fn new(category: MessageCategory, message: String, code: ReportCode) -> Report {\n        Report {\n            category,\n            message,\n            primary_file_ids: Vec::new(),\n            primary: Vec::new(),\n            secondary: Vec::new(),\n            notes: Vec::new(),\n            code,\n        }\n    }\n\n    pub fn error(message: String, code: ReportCode) -> Report {\n        Report::new(MessageCategory::Error, message, code)\n    }\n\n    pub fn warning(message: String, code: ReportCode) -> Report {\n        Report::new(MessageCategory::Warning, message, code)\n    }\n\n    pub fn info(message: String, code: ReportCode) -> Report {\n        Report::new(MessageCategory::Info, message, code)\n    }\n\n    pub fn add_primary(\n        &mut self,\n        location: FileLocation,\n        file_id: FileID,\n        message: String,\n    ) -> &mut Self {\n        let label = ReportLabel::primary(file_id, location).with_message(message);\n        self.primary_mut().push(label);\n        self.primary_file_ids_mut().push(file_id);\n        self\n    }\n\n    pub fn add_secondary(\n        &mut self,\n        location: FileLocation,\n        file_id: FileID,\n        possible_message: Option<String>,\n    ) -> &mut Self {\n        let mut label = ReportLabel::secondary(file_id, location);\n        if let Some(message) = possible_message {\n            label = label.with_message(message);\n        }\n        self.secondary_mut().push(label);\n        self\n    }\n\n    pub fn add_note(&mut self, note: String) -> &mut Self {\n        self.notes_mut().push(note);\n        self\n    }\n\n    pub fn to_diagnostic(&self, verbose: bool) -> Diagnostic<FileID> {\n        let mut labels = self.primary().clone();\n        let mut secondary = self.secondary().clone();\n        labels.append(&mut secondary);\n\n        let diagnostic = match self.category() {\n            MessageCategory::Error => Diagnostic::error(),\n            MessageCategory::Warning => Diagnostic::warning(),\n            MessageCategory::Info => Diagnostic::note(),\n        }\n        .with_message(self.message())\n        .with_labels(labels);\n\n        let mut notes = self.notes().clone();\n        if let Some(url) = self.code().url() {\n            // Add URL to documentation if available.\n            notes.push(format!(\"For more details, see {url}.\"));\n        }\n        if verbose {\n            // Add report code and note on `--allow ID`.\n            notes.push(format!(\"To ignore this type of result, use `--allow {}`.\", self.id()));\n            diagnostic.with_code(self.id()).with_notes(notes)\n        } else {\n            diagnostic.with_notes(notes)\n        }\n    }\n\n    pub fn primary_file_ids(&self) -> &Vec<FileID> {\n        &self.primary_file_ids\n    }\n\n    fn primary_file_ids_mut(&mut self) -> &mut Vec<FileID> {\n        &mut self.primary_file_ids\n    }\n\n    pub fn category(&self) -> &MessageCategory {\n        &self.category\n    }\n\n    pub fn message(&self) -> &String {\n        &self.message\n    }\n\n    pub fn primary(&self) -> &Vec<ReportLabel> {\n        &self.primary\n    }\n\n    fn primary_mut(&mut self) -> &mut Vec<ReportLabel> {\n        &mut self.primary\n    }\n\n    pub fn secondary(&self) -> &Vec<ReportLabel> {\n        &self.secondary\n    }\n\n    fn secondary_mut(&mut self) -> &mut Vec<ReportLabel> {\n        &mut self.secondary\n    }\n\n    pub fn notes(&self) -> &Vec<ReportNote> {\n        &self.notes\n    }\n\n    fn notes_mut(&mut self) -> &mut Vec<ReportNote> {\n        &mut self.notes\n    }\n\n    pub fn code(&self) -> &ReportCode {\n        &self.code\n    }\n\n    pub fn id(&self) -> String {\n        self.code.id()\n    }\n\n    pub fn name(&self) -> String {\n        self.code.name()\n    }\n}\n"
  },
  {
    "path": "program_structure/src/program_library/report_code.rs",
    "content": "const DOC_URL: &str = \"https://github.com/trailofbits/circomspect/blob/main/doc/analysis_passes.md\";\n\n#[derive(Copy, Clone)]\npub enum ReportCode {\n    AssertWrongType,\n    ParseFail,\n    CompilerVersionError,\n    WrongTypesInAssignOperation,\n    WrongNumberOfArguments(usize, usize),\n    UndefinedFunction,\n    UndefinedTemplate,\n    UninitializedSymbolInExpression,\n    UnableToTypeFunction,\n    UnreachableConstraints,\n    UnknownIndex,\n    UnknownDimension,\n    SameFunctionDeclaredTwice,\n    SameTemplateDeclaredTwice,\n    SameSymbolDeclaredTwice,\n    StaticInfoWasOverwritten,\n    SignalInLineInitialization,\n    SignalOutsideOriginalScope,\n    FunctionWrongNumberOfArguments,\n    FunctionInconsistentTyping,\n    FunctionPathWithoutReturn,\n    FunctionReturnError,\n    ForbiddenDeclarationInFunction,\n    NonHomogeneousArray,\n    NonBooleanCondition,\n    NonCompatibleBranchTypes,\n    NonEqualTypesInExpression,\n    NonExistentSymbol,\n    NoMainFoundInProject,\n    NoCompilerVersionWarning,\n    MultipleMainInComponent,\n    TemplateCallAsArgument,\n    TemplateWrongNumberOfArguments,\n    TemplateWithReturnStatement,\n    TypeCantBeUseAsCondition,\n    EmptyArrayInlineDeclaration,\n    PrefixOperatorWithWrongTypes,\n    InfixOperatorWithWrongTypes,\n    InvalidArgumentInCall,\n    InconsistentReturnTypesInBlock,\n    InconsistentStaticInformation,\n    InvalidArrayAccess,\n    InvalidSignalAccess,\n    InvalidArraySize,\n    InvalidArrayType,\n    ForStatementIllConstructed,\n    BadArrayAccess,\n    AssigningAComponentTwice,\n    AssigningASignalTwice,\n    NotAllowedOperation,\n    ConstraintGeneratorInFunction,\n    WrongSignalTags,\n    InvalidPartialArray,\n    MustBeSingleArithmetic,\n    ExpectedDimDiffGotDim(usize, usize),\n    RuntimeError,\n    UnknownTemplate,\n    NonQuadratic,\n    NonConstantArrayLength,\n    NonComputableExpression,\n    AnonymousComponentError,\n    TupleError,\n    // Constraint analysis codes\n    UnconstrainedSignal,\n    OneConstraintIntermediate,\n    NoOutputInInstance,\n    ErrorWat2Wasm,\n    // Circomspect specific codes\n    ShadowingVariable,\n    ParameterNameCollision,\n    FieldElementComparison,\n    FieldElementArithmetic,\n    SignalAssignmentStatement,\n    UnnecessarySignalAssignment,\n    UnusedVariableValue,\n    UnusedParameterValue,\n    VariableWithoutSideEffect,\n    ConstantBranchCondition,\n    NonStrictBinaryConversion,\n    CyclomaticComplexity,\n    TooManyArguments,\n    UnconstrainedLessThan,\n    UnconstrainedDivision,\n    Bn254SpecificCircuit,\n    UnderConstrainedSignal,\n    UnusedOutputSignal,\n}\n\nimpl ReportCode {\n    pub fn id(&self) -> String {\n        use self::ReportCode::*;\n        match self {\n            ParseFail => \"P1000\",\n            NoMainFoundInProject => \"P1001\",\n            MultipleMainInComponent => \"P1002\",\n            CompilerVersionError => \"P1003\",\n            NoCompilerVersionWarning => \"P1004\",\n            WrongTypesInAssignOperation => \"T2000\",\n            UndefinedFunction => \"T2001\",\n            UndefinedTemplate => \"T2002\",\n            UninitializedSymbolInExpression => \"T2003\",\n            UnableToTypeFunction => \"T2004\",\n            UnreachableConstraints => \"T2005\",\n            SameFunctionDeclaredTwice => \"T2006\",\n            SameTemplateDeclaredTwice => \"T2007\",\n            SameSymbolDeclaredTwice => \"T2008\",\n            StaticInfoWasOverwritten => \"T2009\",\n            SignalInLineInitialization => \"T2010\",\n            SignalOutsideOriginalScope => \"T2011\",\n            FunctionWrongNumberOfArguments => \"T2012\",\n            FunctionInconsistentTyping => \"T2013\",\n            FunctionPathWithoutReturn => \"T2014\",\n            FunctionReturnError => \"T2015\",\n            ForbiddenDeclarationInFunction => \"T2016\",\n            NonHomogeneousArray => \"T2017\",\n            NonBooleanCondition => \"T2018\",\n            NonCompatibleBranchTypes => \"T2019\",\n            NonEqualTypesInExpression => \"T2020\",\n            NonExistentSymbol => \"T2021\",\n            TemplateCallAsArgument => \"T2022\",\n            TemplateWrongNumberOfArguments => \"T2023\",\n            TemplateWithReturnStatement => \"T2024\",\n            TypeCantBeUseAsCondition => \"T2025\",\n            EmptyArrayInlineDeclaration => \"T2026\",\n            PrefixOperatorWithWrongTypes => \"T2027\",\n            InfixOperatorWithWrongTypes => \"T2028\",\n            InvalidArgumentInCall => \"T2029\",\n            InconsistentReturnTypesInBlock => \"T2030\",\n            InconsistentStaticInformation => \"T2031\",\n            InvalidArrayAccess => \"T2032\",\n            InvalidSignalAccess => \"T2046\",\n            InvalidArraySize => \"T2033\",\n            InvalidArrayType => \"T2034\",\n            ForStatementIllConstructed => \"T2035\",\n            BadArrayAccess => \"T2035\",\n            AssigningAComponentTwice => \"T2036\",\n            AssigningASignalTwice => \"T2037\",\n            NotAllowedOperation => \"T2038\",\n            ConstraintGeneratorInFunction => \"T2039\",\n            WrongSignalTags => \"T2040\",\n            AssertWrongType => \"T2041\",\n            UnknownIndex => \"T2042\",\n            InvalidPartialArray => \"T2043\",\n            MustBeSingleArithmetic => \"T2044\",\n            ExpectedDimDiffGotDim(..) => \"T2045\",\n            RuntimeError => \"T3001\",\n            UnknownDimension => \"T20460\",\n            UnknownTemplate => \"T20461\",\n            NonQuadratic => \"T20462\",\n            NonConstantArrayLength => \"T20463\",\n            NonComputableExpression => \"T20464\",\n            WrongNumberOfArguments(..) => \"T20465\",\n            AnonymousComponentError => \"TAC01\",\n            TupleError => \"TAC02\",\n            // Constraint analysis codes\n            UnconstrainedSignal => \"CA01\",\n            OneConstraintIntermediate => \"CA02\",\n            NoOutputInInstance => \"CA03\",\n            ErrorWat2Wasm => \"W01\",\n            // Circomspect specific codes\n            ShadowingVariable => \"CS0001\",\n            ParameterNameCollision => \"CS0002\",\n            FieldElementComparison => \"CS0003\",\n            FieldElementArithmetic => \"CS0004\",\n            SignalAssignmentStatement => \"CS0005\",\n            UnusedVariableValue => \"CS0006\",\n            UnusedParameterValue => \"CS0007\",\n            VariableWithoutSideEffect => \"CS0008\",\n            ConstantBranchCondition => \"CS0009\",\n            NonStrictBinaryConversion => \"CS0010\",\n            CyclomaticComplexity => \"CS0011\",\n            TooManyArguments => \"CS0012\",\n            UnnecessarySignalAssignment => \"CS0013\",\n            UnconstrainedLessThan => \"CS0014\",\n            UnconstrainedDivision => \"CS0015\",\n            Bn254SpecificCircuit => \"CS0016\",\n            UnderConstrainedSignal => \"CS0017\",\n            UnusedOutputSignal => \"CS0018\",\n        }\n        .to_string()\n    }\n\n    pub fn name(&self) -> String {\n        use self::ReportCode::*;\n        match self {\n            AssertWrongType => \"assert-wrong-type\",\n            ParseFail => \"parse-fail\",\n            CompilerVersionError => \"compiler-version-error\",\n            WrongTypesInAssignOperation => \"wrong-types-in-assign-operation\",\n            WrongNumberOfArguments(..) => \"wrong-number-of-arguments\",\n            AnonymousComponentError => \"anonymous-component-error\",\n            TupleError => \"tuple-error\",\n            UndefinedFunction => \"undefined-function\",\n            UndefinedTemplate => \"undefined-template\",\n            UninitializedSymbolInExpression => \"uninitialized-symbol-in-expression\",\n            UnableToTypeFunction => \"unable-to-type-function\",\n            UnreachableConstraints => \"unreachable-constraints\",\n            UnknownIndex => \"unknown-index\",\n            UnknownDimension => \"unknown-dimension\",\n            SameFunctionDeclaredTwice => \"same-function-declared-twice\",\n            SameTemplateDeclaredTwice => \"same-template-declared-twice\",\n            SameSymbolDeclaredTwice => \"same-symbol-declared-twice\",\n            StaticInfoWasOverwritten => \"static-info-was-overwritten\",\n            SignalInLineInitialization => \"signal-in-line-initialization\",\n            SignalOutsideOriginalScope => \"signal-outside-original-scope\",\n            FunctionWrongNumberOfArguments => \"function-wrong-number-of-arguments\",\n            FunctionInconsistentTyping => \"function-inconsistent-typing\",\n            FunctionPathWithoutReturn => \"function-path-without-return\",\n            FunctionReturnError => \"function-return-error\",\n            ForbiddenDeclarationInFunction => \"forbidden-declaration-in-function\",\n            NonHomogeneousArray => \"non-homogeneous-array\",\n            NonBooleanCondition => \"non-boolean-condition\",\n            NonCompatibleBranchTypes => \"non-compatible-branch-types\",\n            NonEqualTypesInExpression => \"non-equal-types-in-expression\",\n            NonExistentSymbol => \"non-existent-symbol\",\n            NoMainFoundInProject => \"no-main-found-in-project\",\n            NoCompilerVersionWarning => \"no-compiler-version-warning\",\n            MultipleMainInComponent => \"multiple-main-in-component\",\n            TemplateCallAsArgument => \"template-call-as-argument\",\n            TemplateWrongNumberOfArguments => \"template-wrong-number-of-arguments\",\n            TemplateWithReturnStatement => \"template-with-return-statement\",\n            TypeCantBeUseAsCondition => \"type-cant-be-use-as-condition\",\n            EmptyArrayInlineDeclaration => \"empty-array-inline-declaration\",\n            PrefixOperatorWithWrongTypes => \"prefix-operator-with-wrong-types\",\n            InfixOperatorWithWrongTypes => \"infix-operator-with-wrong-types\",\n            InvalidArgumentInCall => \"invalid-argument-in-call\",\n            InconsistentReturnTypesInBlock => \"inconsistent-return-types-in-block\",\n            InconsistentStaticInformation => \"inconsistent-static-information\",\n            InvalidArrayAccess => \"invalid-array-access\",\n            InvalidSignalAccess => \"invalid-signal-access\",\n            InvalidArraySize => \"invalid-array-size\",\n            InvalidArrayType => \"invalid-array-type\",\n            ForStatementIllConstructed => \"for-statement-ill-constructed\",\n            BadArrayAccess => \"bad-array-access\",\n            AssigningAComponentTwice => \"assigning-a-component-twice\",\n            AssigningASignalTwice => \"assigning-a-signal-twice\",\n            NotAllowedOperation => \"not-allowed-operation\",\n            ConstraintGeneratorInFunction => \"constraint-generator-in-function\",\n            WrongSignalTags => \"wrong-signal-tags\",\n            InvalidPartialArray => \"invalid-partial-array\",\n            MustBeSingleArithmetic => \"must-be-single-arithmetic\",\n            ExpectedDimDiffGotDim(..) => \"expected-dim-diff-got-dim\",\n            RuntimeError => \"runtime-error\",\n            UnknownTemplate => \"unknown-template\",\n            NonQuadratic => \"non-quadratic\",\n            NonConstantArrayLength => \"non-constant-array-length\",\n            NonComputableExpression => \"non-computable-expression\",\n            UnconstrainedSignal => \"unconstrained-signal\",\n            OneConstraintIntermediate => \"one-constraint-intermediate\",\n            NoOutputInInstance => \"no-output-in-instance\",\n            ErrorWat2Wasm => \"error-wat2-wasm\",\n            ShadowingVariable => \"shadowing-variable\",\n            ParameterNameCollision => \"parameter-name-collision\",\n            FieldElementComparison => \"field-element-comparison\",\n            FieldElementArithmetic => \"field-element-arithmetic\",\n            SignalAssignmentStatement => \"signal-assignment-statement\",\n            UnnecessarySignalAssignment => \"unnecessary-signal-assignment\",\n            UnusedVariableValue => \"unused-variable-value\",\n            UnusedParameterValue => \"unused-parameter-value\",\n            VariableWithoutSideEffect => \"variable-without-side-effect\",\n            ConstantBranchCondition => \"constant-branch-condition\",\n            NonStrictBinaryConversion => \"non-strict-binary-conversion\",\n            CyclomaticComplexity => \"cyclomatic-complexity\",\n            TooManyArguments => \"too-many-arguments\",\n            UnconstrainedLessThan => \"unconstrained-less-than\",\n            UnconstrainedDivision => \"unconstrained-division\",\n            Bn254SpecificCircuit => \"bn254-specific-circuit\",\n            UnderConstrainedSignal => \"under-constrained-signal\",\n            UnusedOutputSignal => \"unused-output-signal\",\n        }\n        .to_string()\n    }\n\n    pub fn url(&self) -> Option<String> {\n        use ReportCode::*;\n        match self {\n            ShadowingVariable => Some(\"shadowing-variable\"),\n            FieldElementComparison => Some(\"field-element-comparison\"),\n            FieldElementArithmetic => Some(\"field-element-arithmetic\"),\n            SignalAssignmentStatement => Some(\"signal-assignment\"),\n            UnusedVariableValue => Some(\"unused-variable-or-parameter\"),\n            UnusedParameterValue => Some(\"unused-variable-or-parameter\"),\n            VariableWithoutSideEffect => Some(\"side-effect-free-assignment\"),\n            ConstantBranchCondition => Some(\"constant-branch-condition\"),\n            NonStrictBinaryConversion => Some(\"non-strict-binary-conversion\"),\n            CyclomaticComplexity => Some(\"overly-complex-function-or-template\"),\n            TooManyArguments => Some(\"overly-complex-function-or-template\"),\n            UnnecessarySignalAssignment => Some(\"unnecessary-signal-assignment\"),\n            UnconstrainedLessThan => Some(\"unconstrained-less-than\"),\n            UnconstrainedDivision => Some(\"unconstrained-division\"),\n            Bn254SpecificCircuit => Some(\"bn254-specific-circuit\"),\n            UnderConstrainedSignal => Some(\"under-constrained-signal\"),\n            UnusedOutputSignal => Some(\"unused-output-signal\"),\n            // We only provide a URL for Circomspect specific issues.\n            _ => None,\n        }\n        .map(|section| format!(\"{DOC_URL}#{section}\"))\n    }\n}\n"
  },
  {
    "path": "program_structure/src/program_library/template_data.rs",
    "content": "use super::ast;\nuse super::ast::{FillMeta, Statement};\nuse super::file_definition::FileID;\nuse crate::file_definition::FileLocation;\nuse std::collections::{HashMap, HashSet, BTreeMap};\n\npub type TagInfo = HashSet<String>;\npub type TemplateInfo = HashMap<String, TemplateData>;\ntype SignalInfo = BTreeMap<String, (usize, TagInfo)>;\ntype SignalDeclarationOrder = Vec<(String, usize)>;\n\n#[derive(Clone)]\npub struct TemplateData {\n    file_id: FileID,\n    name: String,\n    body: Statement,\n    num_of_params: usize,\n    name_of_params: Vec<String>,\n    param_location: FileLocation,\n    input_signals: SignalInfo,\n    output_signals: SignalInfo,\n    is_parallel: bool,\n    is_custom_gate: bool,\n    // Only used to know the order in which signals are declared.\n    input_declarations: SignalDeclarationOrder,\n    output_declarations: SignalDeclarationOrder,\n}\n\nimpl TemplateData {\n    #[allow(clippy::too_many_arguments)]\n    pub fn new(\n        name: String,\n        file_id: FileID,\n        mut body: Statement,\n        num_of_params: usize,\n        name_of_params: Vec<String>,\n        param_location: FileLocation,\n        elem_id: &mut usize,\n        is_parallel: bool,\n        is_custom_gate: bool,\n    ) -> TemplateData {\n        body.fill(file_id, elem_id);\n        let mut input_signals = SignalInfo::new();\n        let mut output_signals = SignalInfo::new();\n        let mut input_declarations = SignalDeclarationOrder::new();\n        let mut output_declarations = SignalDeclarationOrder::new();\n        fill_inputs_and_outputs(\n            &body,\n            &mut input_signals,\n            &mut output_signals,\n            &mut input_declarations,\n            &mut output_declarations,\n        );\n        TemplateData {\n            name,\n            file_id,\n            body,\n            num_of_params,\n            name_of_params,\n            param_location,\n            input_signals,\n            output_signals,\n            is_parallel,\n            is_custom_gate,\n            input_declarations,\n            output_declarations,\n        }\n    }\n\n    pub fn get_file_id(&self) -> FileID {\n        self.file_id\n    }\n\n    pub fn get_body(&self) -> &Statement {\n        &self.body\n    }\n\n    pub fn get_body_as_vec(&self) -> &Vec<Statement> {\n        match &self.body {\n            Statement::Block { stmts, .. } => stmts,\n            _ => panic!(\"Function body should be a block\"),\n        }\n    }\n\n    pub fn get_mut_body(&mut self) -> &mut Statement {\n        &mut self.body\n    }\n\n    pub fn get_mut_body_as_vec(&mut self) -> &mut Vec<Statement> {\n        match &mut self.body {\n            Statement::Block { stmts, .. } => stmts,\n            _ => panic!(\"Function body should be a block\"),\n        }\n    }\n\n    pub fn get_num_of_params(&self) -> usize {\n        self.num_of_params\n    }\n\n    pub fn get_param_location(&self) -> FileLocation {\n        self.param_location.clone()\n    }\n\n    pub fn get_name_of_params(&self) -> &Vec<String> {\n        &self.name_of_params\n    }\n\n    pub fn get_input_info(&self, name: &str) -> Option<&(usize, TagInfo)> {\n        self.input_signals.get(name)\n    }\n\n    pub fn get_output_info(&self, name: &str) -> Option<&(usize, TagInfo)> {\n        self.output_signals.get(name)\n    }\n    pub fn get_inputs(&self) -> &SignalInfo {\n        &self.input_signals\n    }\n    pub fn get_outputs(&self) -> &SignalInfo {\n        &self.output_signals\n    }\n    pub fn get_declaration_inputs(&self) -> &SignalDeclarationOrder {\n        &self.input_declarations\n    }\n    pub fn get_declaration_outputs(&self) -> &SignalDeclarationOrder {\n        &self.output_declarations\n    }\n    pub fn get_name(&self) -> &str {\n        &self.name\n    }\n    pub fn is_parallel(&self) -> bool {\n        self.is_parallel\n    }\n    pub fn is_custom_gate(&self) -> bool {\n        self.is_custom_gate\n    }\n}\n\nfn fill_inputs_and_outputs(\n    template_statement: &Statement,\n    input_signals: &mut SignalInfo,\n    output_signals: &mut SignalInfo,\n    input_declarations: &mut SignalDeclarationOrder,\n    output_declarations: &mut SignalDeclarationOrder,\n) {\n    match template_statement {\n        Statement::IfThenElse { if_case, else_case, .. } => {\n            fill_inputs_and_outputs(\n                if_case,\n                input_signals,\n                output_signals,\n                input_declarations,\n                output_declarations,\n            );\n            if let Option::Some(else_value) = else_case {\n                fill_inputs_and_outputs(\n                    else_value,\n                    input_signals,\n                    output_signals,\n                    input_declarations,\n                    output_declarations,\n                );\n            }\n        }\n        Statement::Block { stmts, .. } => {\n            for stmt in stmts.iter() {\n                fill_inputs_and_outputs(\n                    stmt,\n                    input_signals,\n                    output_signals,\n                    input_declarations,\n                    output_declarations,\n                );\n            }\n        }\n        Statement::While { stmt, .. } => {\n            fill_inputs_and_outputs(\n                stmt,\n                input_signals,\n                output_signals,\n                input_declarations,\n                output_declarations,\n            );\n        }\n        Statement::InitializationBlock { initializations, .. } => {\n            for initialization in initializations.iter() {\n                fill_inputs_and_outputs(\n                    initialization,\n                    input_signals,\n                    output_signals,\n                    input_declarations,\n                    output_declarations,\n                );\n            }\n        }\n        Statement::Declaration {\n            xtype: ast::VariableType::Signal(stype, tag_list),\n            name,\n            dimensions,\n            ..\n        } => {\n            let signal_name = name.clone();\n            let dimensions = dimensions.len();\n            let mut tag_info = HashSet::new();\n            for tag in tag_list {\n                tag_info.insert(tag.clone());\n            }\n\n            match stype {\n                ast::SignalType::Input => {\n                    input_signals.insert(signal_name.clone(), (dimensions, tag_info));\n                    input_declarations.push((signal_name, dimensions));\n                }\n                ast::SignalType::Output => {\n                    output_signals.insert(signal_name.clone(), (dimensions, tag_info));\n                    output_declarations.push((signal_name, dimensions));\n                }\n                _ => {} //no need to deal with intermediate signals\n            }\n        }\n        _ => {}\n    }\n}\n"
  },
  {
    "path": "program_structure/src/program_library/template_library.rs",
    "content": "use std::collections::HashMap;\n\nuse crate::ast::Definition;\nuse crate::file_definition::{FileID, FileLibrary};\nuse crate::function_data::{FunctionData, FunctionInfo};\nuse crate::template_data::{TemplateData, TemplateInfo};\n\ntype Contents = HashMap<FileID, Vec<Definition>>;\n\npub struct TemplateLibrary {\n    pub functions: FunctionInfo,\n    pub templates: TemplateInfo,\n    pub file_library: FileLibrary,\n}\n\nimpl TemplateLibrary {\n    pub fn new(library_contents: Contents, file_library: FileLibrary) -> TemplateLibrary {\n        let mut functions = HashMap::new();\n        let mut templates = HashMap::new();\n\n        let mut elem_id = 0;\n        for (file_id, file_contents) in library_contents {\n            for definition in file_contents {\n                match definition {\n                    Definition::Function { name, args, arg_location, body, .. } => {\n                        functions.insert(\n                            name.clone(),\n                            FunctionData::new(\n                                name,\n                                file_id,\n                                body,\n                                args.len(),\n                                args,\n                                arg_location,\n                                &mut elem_id,\n                            ),\n                        );\n                    }\n                    Definition::Template {\n                        name,\n                        args,\n                        arg_location,\n                        body,\n                        parallel,\n                        is_custom_gate,\n                        ..\n                    } => {\n                        templates.insert(\n                            name.clone(),\n                            TemplateData::new(\n                                name,\n                                file_id,\n                                body,\n                                args.len(),\n                                args,\n                                arg_location,\n                                &mut elem_id,\n                                parallel,\n                                is_custom_gate,\n                            ),\n                        );\n                    }\n                }\n            }\n        }\n        TemplateLibrary { functions, templates, file_library }\n    }\n    // Template methods.\n    pub fn contains_template(&self, template_name: &str) -> bool {\n        self.templates.contains_key(template_name)\n    }\n\n    pub fn get_template(&self, template_name: &str) -> &TemplateData {\n        assert!(self.contains_template(template_name));\n        self.templates.get(template_name).unwrap()\n    }\n\n    pub fn get_template_mut(&mut self, template_name: &str) -> &mut TemplateData {\n        assert!(self.contains_template(template_name));\n        self.templates.get_mut(template_name).unwrap()\n    }\n\n    pub fn get_templates(&self) -> &TemplateInfo {\n        &self.templates\n    }\n\n    pub fn get_templates_mut(&mut self) -> &mut TemplateInfo {\n        &mut self.templates\n    }\n\n    // Function methods.\n    pub fn contains_function(&self, function_name: &str) -> bool {\n        self.functions.contains_key(function_name)\n    }\n\n    pub fn get_function(&self, function_name: &str) -> &FunctionData {\n        assert!(self.contains_function(function_name));\n        self.functions.get(function_name).unwrap()\n    }\n\n    pub fn get_function_mut(&mut self, function_name: &str) -> &mut FunctionData {\n        assert!(self.contains_function(function_name));\n        self.functions.get_mut(function_name).unwrap()\n    }\n\n    pub fn get_functions(&self) -> &FunctionInfo {\n        &self.functions\n    }\n\n    pub fn get_functions_mut(&mut self) -> &mut FunctionInfo {\n        &mut self.functions\n    }\n\n    pub fn get_file_library(&self) -> &FileLibrary {\n        &self.file_library\n    }\n}\n"
  },
  {
    "path": "program_structure/src/static_single_assignment/dominator_tree.rs",
    "content": "use log::trace;\nuse std::collections::HashSet;\nuse std::marker::PhantomData;\n\nuse super::traits::DirectedGraphNode;\n\ntype Index = usize;\ntype DominatorInfo = Vec<HashSet<Index>>;\ntype ImmediateDominatorInfo = Vec<Option<Index>>;\n\n// A structure which encapsulates the dominance relation on a CFG.\npub struct DominatorTree<T: DirectedGraphNode> {\n    dominators: DominatorInfo,\n    immediate_dominators: ImmediateDominatorInfo,\n    dominator_successors: DominatorInfo,\n    dominance_frontier: DominatorInfo,\n    marker: PhantomData<T>,\n}\n\nimpl<T: DirectedGraphNode> DominatorTree<T> {\n    pub fn new(basic_blocks: &[T]) -> DominatorTree<T> {\n        let dominators = compute_dominators(basic_blocks);\n        let (immediate_dominators, dominator_successors) =\n            compute_immediate_dominators(basic_blocks, &dominators);\n        let dominance_frontier = compute_dominance_frontier(basic_blocks, &immediate_dominators);\n        // We assume that the first block (with index 0) represents the entry block.\n        assert!(immediate_dominators[0].is_none());\n        DominatorTree {\n            dominators,\n            immediate_dominators,\n            dominator_successors,\n            dominance_frontier,\n            marker: PhantomData,\n        }\n    }\n\n    pub fn entry_block(&self) -> Index {\n        Index::default()\n    }\n\n    pub fn get_dominators(&self, i: Index) -> HashSet<Index> {\n        self.dominators[i].clone()\n    }\n\n    pub fn get_immediate_dominator(&self, i: Index) -> Option<Index> {\n        self.immediate_dominators[i]\n    }\n\n    pub fn get_dominator_successors(&self, i: Index) -> HashSet<Index> {\n        self.dominator_successors[i].clone()\n    }\n\n    pub fn get_dominance_frontier(&self, i: Index) -> HashSet<Index> {\n        self.dominance_frontier[i].clone()\n    }\n}\n\n// This is a stupid simple (quadratic) algorithm based on an iterative data-flow analysis.\nfn compute_dominators<T: DirectedGraphNode>(basic_blocks: &[T]) -> DominatorInfo {\n    let mut dominators = Vec::new();\n    let nof_blocks = basic_blocks.len();\n    dominators.push(HashSet::from([0]));\n    for _ in 1..basic_blocks.len() {\n        dominators.push((0..nof_blocks).collect());\n    }\n\n    let mut done = false;\n    while !done {\n        done = true;\n        for i in 1..nof_blocks {\n            let mut new_dominators: HashSet<usize> = (0..nof_blocks).collect();\n            for &j in basic_blocks[i].predecessors() {\n                new_dominators = new_dominators.intersection(&dominators[j]).copied().collect();\n            }\n            new_dominators.insert(i);\n            if new_dominators != dominators[i] {\n                dominators[i] = new_dominators;\n                done = false;\n            }\n        }\n    }\n    dominators\n}\n\n// Compute immediate dominators (a `Vec<Option<usize>>`) and the dominator tree relation (a\n// `Vec<HashSet<usize>>`). (Note that the entry block of the CFG has no immediate dominator.)\nfn compute_immediate_dominators<T: DirectedGraphNode>(\n    basic_blocks: &[T],\n    dominators: &DominatorInfo,\n) -> (ImmediateDominatorInfo, DominatorInfo) {\n    let nof_blocks = basic_blocks.len();\n    let mut immediate_dominators = vec![None; nof_blocks];\n    let mut dominator_successors = vec![HashSet::new(); nof_blocks];\n\n    for i in 0..nof_blocks {\n        trace!(\"the dominator set of block {i} is {:?}\", dominators[i]);\n        let mut idom_candidates: HashSet<usize> = dominators[i].clone();\n        idom_candidates.remove(&i);\n\n        if idom_candidates.len() > 1 {\n            // The set `all_dominators` is the strict up set of the nodes dominators. I.e.\n            //\n            //     `all_dominators(i) = U {Dom(j) - {j}; j strictly dominates i}`.\n            //\n            // The immediate dominator of the node will be the unique element in the set\n            // `idom_candidates - all_dominators` when this set is non-empty.\n            let mut all_dominators: HashSet<usize> = HashSet::new();\n            for j in &idom_candidates {\n                // 'all_dominators' is upwards closed.\n                if all_dominators.contains(j) {\n                    continue;\n                }\n                // Set `all_dominators = all_dominators U (Dom(i) \\ {i}`.\n                all_dominators = dominators[*j]\n                    .clone()\n                    .into_iter()\n                    .filter(|&k| k != *j) // Remove i.\n                    .collect::<HashSet<usize>>()\n                    .union(&all_dominators)\n                    .copied()\n                    .collect();\n            }\n            idom_candidates = &idom_candidates - &all_dominators;\n            assert!(idom_candidates.len() <= 1);\n        }\n        if let Some(&j) = idom_candidates.iter().next() {\n            trace!(\"the immediate dominator of {i} is {j}\");\n            immediate_dominators[i] = Some(j);\n            dominator_successors[j].insert(i);\n        }\n    }\n    (immediate_dominators, dominator_successors)\n}\n\n// Compute dominance frontiers (a `Vec<HashSet<usize>>`) of all nodes. The node\n// `i` is in the _dominance frontier_ of the node `j` if `j` dominates an\n// immediate predecessor of `i`, but `j` does not strictly dominate `i`.\nfn compute_dominance_frontier<T: DirectedGraphNode>(\n    basic_blocks: &[T],\n    immediate_dominators: &ImmediateDominatorInfo,\n) -> DominatorInfo {\n    let nof_blocks = basic_blocks.len();\n    let mut dominance_frontier = vec![HashSet::new(); nof_blocks];\n    for i in 0..nof_blocks {\n        if basic_blocks[i].predecessors().len() > 1 {\n            for &j in basic_blocks[i].predecessors() {\n                let mut k = j;\n                while Some(k) != immediate_dominators[i] {\n                    dominance_frontier[k].insert(i);\n                    k = match immediate_dominators[k] {\n                        Some(idom) => idom,\n                        None => break,\n                    };\n                }\n            }\n        }\n    }\n    dominance_frontier\n}\n"
  },
  {
    "path": "program_structure/src/static_single_assignment/errors.rs",
    "content": "use crate::report_code::ReportCode;\nuse crate::report::Report;\nuse crate::file_definition::{FileID, FileLocation};\n\n/// Error enum for SSA generation errors.\n#[derive(Debug)]\npub enum SSAError {\n    /// The variable is read before it is declared/written.\n    UndefinedVariableError { name: String, file_id: Option<FileID>, location: FileLocation },\n}\n\npub type SSAResult<T> = Result<T, SSAError>;\n\nimpl SSAError {\n    pub fn into_report(&self) -> Report {\n        use SSAError::*;\n        match self {\n            UndefinedVariableError { name, file_id, location } => {\n                let mut report = Report::error(\n                    format!(\"The variable `{name}` is used before it is defined.\"),\n                    ReportCode::UninitializedSymbolInExpression,\n                );\n                if let Some(file_id) = file_id {\n                    report.add_primary(\n                        location.clone(),\n                        *file_id,\n                        format!(\"The variable `{name}` is first seen here.\"),\n                    );\n                }\n                report\n            }\n        }\n    }\n}\n\nimpl From<SSAError> for Report {\n    fn from(error: SSAError) -> Report {\n        error.into_report()\n    }\n}\n"
  },
  {
    "path": "program_structure/src/static_single_assignment/mod.rs",
    "content": "//! This module implements a generic conversion into single-static assignment\n//! form.\npub mod dominator_tree;\npub mod errors;\npub mod traits;\n\nuse log::trace;\n\nuse dominator_tree::DominatorTree;\nuse errors::SSAResult;\nuse traits::*;\n\n/// Insert a dummy phi statement in block `j`, for each variable written in block\n/// `i`, if `j` is in the dominance frontier of `i`.\npub fn insert_phi_statements<Cfg: SSAConfig>(\n    basic_blocks: &mut [Cfg::BasicBlock],\n    dominator_tree: &DominatorTree<Cfg::BasicBlock>,\n    env: &mut Cfg::Environment,\n) {\n    // Insert phi statements at the dominance frontier of each block.\n    let mut work_list: Vec<Index> = (0..basic_blocks.len()).collect();\n    while let Some(current_index) = work_list.pop() {\n        let variables_written = {\n            let current_block = &basic_blocks[current_index];\n            current_block.variables_written().clone()\n        };\n        if variables_written.is_empty() {\n            trace!(\"basic block {current_index} does not write any variables\");\n            continue;\n        }\n        trace!(\n            \"dominance frontier for block {current_index} is {:?}\",\n            dominator_tree.get_dominance_frontier(current_index)\n        );\n        for frontier_index in dominator_tree.get_dominance_frontier(current_index) {\n            let frontier_block = &mut basic_blocks[frontier_index];\n            for var in &variables_written {\n                if !frontier_block.has_phi_statement(var) {\n                    // If a phi statement was added to the block we need to\n                    // re-add the block to the work list.\n                    frontier_block.insert_phi_statement(var, env);\n                    work_list.push(frontier_index);\n                }\n            }\n        }\n    }\n}\n\n/// Traverses the dominator tree in pre-order and for each block, the function\n///\n/// 1. Renames all variables to SSA form, keeping track of the current\n///    version of each variable.\n/// 2. Updates phi expression arguments in each successor of the current\n///    block, adding the correct versioned arguments to the expression.\npub fn insert_ssa_variables<Cfg: SSAConfig>(\n    basic_blocks: &mut [Cfg::BasicBlock],\n    dominator_tree: &DominatorTree<Cfg::BasicBlock>,\n    env: &mut Cfg::Environment,\n) -> SSAResult<()> {\n    insert_ssa_variables_impl::<Cfg>(0, basic_blocks, dominator_tree, env)?;\n    Ok(())\n}\n\nfn insert_ssa_variables_impl<Cfg: SSAConfig>(\n    current_index: Index,\n    basic_blocks: &mut [Cfg::BasicBlock],\n    dominator_tree: &DominatorTree<Cfg::BasicBlock>,\n    env: &mut Cfg::Environment,\n) -> SSAResult<()> {\n    // 1. Update variables in current block.\n    let successors = {\n        let current_block =\n            basic_blocks.get_mut(current_index).expect(\"invalid block index during SSA generation\");\n        current_block.insert_ssa_variables(env)?;\n        current_block.successors().clone()\n    };\n    // 2. Update phi statements in successor blocks.\n    for successor_index in successors {\n        let successor_block = basic_blocks\n            .get_mut(successor_index)\n            .expect(\"invalid block index during SSA generation\");\n        successor_block.update_phi_statements(env);\n    }\n    // 3. Update dominator tree successors recursively.\n    for successor_index in dominator_tree.get_dominator_successors(current_index) {\n        env.add_variable_scope();\n        insert_ssa_variables_impl::<Cfg>(successor_index, basic_blocks, dominator_tree, env)?;\n        env.remove_variable_scope();\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "program_structure/src/static_single_assignment/traits.rs",
    "content": "use log::trace;\nuse std::collections::HashSet;\nuse std::hash::Hash;\n\nuse super::errors::SSAResult;\n\npub trait SSAConfig: Sized {\n    /// The type used to track variable versions.\n    type Version;\n\n    /// The type of a variable.\n    type Variable: PartialEq + Eq + Hash + Clone;\n\n    /// An environment type used to track version across the CFG.\n    type Environment: SSAEnvironment;\n\n    /// The type of a statement.\n    type Statement: SSAStatement<Self>;\n\n    /// The type of a basic block.\n    type BasicBlock: SSABasicBlock<Self> + DirectedGraphNode;\n}\n\n/// An environment used to track variable versions across a CFG.\npub trait SSAEnvironment {\n    /// Enter variable scope.\n    fn add_variable_scope(&mut self);\n\n    /// Leave variable scope.\n    fn remove_variable_scope(&mut self);\n}\n\n/// A basic block containing a (possibly empty) list of statements.\npub trait SSABasicBlock<Cfg: SSAConfig>: DirectedGraphNode {\n    /// Add the given statement to the front of the basic block.\n    fn prepend_statement(&mut self, stmt: Cfg::Statement);\n\n    /// Returns an iterator over the statements of the basic block.\n    ///\n    /// Note: We have to use dynamic dispatch here because returning `impl\n    /// Trait` from trait methods is not a thing yet. For details, see\n    /// rust-lang.github.io/impl-trait-initiative/RFCs/rpit-in-traits.html)\n    fn statements<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Cfg::Statement> + 'a>;\n\n    /// Returns an iterator over mutable references to the statements of the\n    /// basic block.\n    ///\n    /// Note: We have to use dynamic dispatch here because returning `impl\n    /// Trait` from trait methods is not a thing yet. For details, see\n    /// rust-lang.github.io/impl-trait-initiative/RFCs/rpit-in-traits.html)\n    fn statements_mut<'a>(&'a mut self) -> Box<dyn Iterator<Item = &'a mut Cfg::Statement> + 'a>;\n\n    /// Returns the set of variables written by the basic block.\n    fn variables_written(&self) -> HashSet<Cfg::Variable> {\n        self.statements().fold(HashSet::new(), |mut vars, stmt| {\n            vars.extend(stmt.variables_written());\n            vars\n        })\n    }\n\n    /// Returns true if the basic block has a phi statement for the given\n    /// variable.\n    fn has_phi_statement(&self, var: &Cfg::Variable) -> bool {\n        self.statements().any(|stmt| stmt.is_phi_statement_for(var))\n    }\n\n    /// Inserts a new phi statement for the given variable at the top of the basic\n    /// block.\n    fn insert_phi_statement(&mut self, var: &Cfg::Variable, env: &Cfg::Environment) {\n        self.prepend_statement(SSAStatement::new_phi_statement(var, env));\n    }\n\n    /// Updates the RHS of each phi statement in the basic block with the SSA\n    /// variable versions from the given environment.\n    fn update_phi_statements(&mut self, env: &Cfg::Environment) {\n        trace!(\"updating phi expression arguments in block {}\", self.index());\n        for stmt in self.statements_mut() {\n            if stmt.is_phi_statement() {\n                stmt.ensure_phi_argument(env);\n            } else {\n                // Since phi statements proceed all other statements we are done\n                // here.\n                break;\n            }\n        }\n    }\n\n    /// Updates each variable to the corresponding SSA variable, in each\n    /// statement in the basic block.\n    fn insert_ssa_variables(&mut self, env: &mut Cfg::Environment) -> SSAResult<()> {\n        trace!(\"inserting SSA variables in block {}\", self.index());\n        for stmt in self.statements_mut() {\n            stmt.insert_ssa_variables(env)?;\n        }\n        Ok(())\n    }\n}\n\n/// A statement in the language.\npub trait SSAStatement<Cfg: SSAConfig>: Clone {\n    /// Returns the set of variables written by statement.\n    fn variables_written(&self) -> HashSet<Cfg::Variable>;\n\n    /// Returns a new phi statement (with empty RHS) for the given variable.\n    fn new_phi_statement(name: &Cfg::Variable, env: &Cfg::Environment) -> Self;\n\n    /// Returns true iff the statement is a phi statement.\n    fn is_phi_statement(&self) -> bool;\n\n    /// Returns true iff the statement is a phi statement for the given variable.\n    fn is_phi_statement_for(&self, var: &Cfg::Variable) -> bool;\n\n    /// Ensure that the phi expression argument list of a phi statement contains the\n    /// current version of the variable, according to the given environment.\n    ///\n    /// Panics if the statement is not a phi statement.\n    fn ensure_phi_argument(&mut self, env: &Cfg::Environment);\n\n    /// Replace each variable occurring in the statement by the corresponding\n    /// versioned SSA variable.\n    fn insert_ssa_variables(&mut self, env: &mut Cfg::Environment) -> SSAResult<()>;\n}\n\npub type Index = usize;\npub type IndexSet = HashSet<Index>;\n\n/// This trait is used to make graph algorithms (like dominator tree and dominator\n/// frontier generation) generic over the graph node type for unit testing purposes.\npub trait DirectedGraphNode {\n    fn index(&self) -> Index;\n\n    fn predecessors(&self) -> &IndexSet;\n\n    fn successors(&self) -> &IndexSet;\n}\n"
  },
  {
    "path": "program_structure/src/utils/constants.rs",
    "content": "use anyhow::{anyhow, Error};\nuse num_bigint::BigInt;\nuse std::fmt;\nuse std::str::FromStr;\n\n#[derive(Default, Clone, PartialEq, Eq)]\npub enum Curve {\n    #[default] // Used for testing.\n    Bn254,\n    Bls12_381,\n    Goldilocks,\n}\n\nimpl fmt::Display for Curve {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        use Curve::*;\n        match self {\n            Bn254 => write!(f, \"BN254\"),\n            Bls12_381 => write!(f, \"BLS12_381\"),\n            Goldilocks => write!(f, \"Goldilocks\"),\n        }\n    }\n}\n\nimpl fmt::Debug for Curve {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"{self}\")\n    }\n}\n\nimpl Curve {\n    fn prime(&self) -> BigInt {\n        use Curve::*;\n        let prime = match self {\n            Bn254 => {\n                \"21888242871839275222246405745257275088548364400416034343698204186575808495617\"\n            }\n            Bls12_381 => {\n                \"52435875175126190479447740508185965837690552500527637822603658699938581184513\"\n            }\n            Goldilocks => \"18446744069414584321\",\n        };\n        BigInt::parse_bytes(prime.as_bytes(), 10).expect(\"failed to parse prime\")\n    }\n}\n\nimpl FromStr for Curve {\n    type Err = Error;\n\n    fn from_str(curve: &str) -> Result<Self, Self::Err> {\n        match &curve.to_uppercase()[..] {\n            \"BN254\" => Ok(Curve::Bn254),\n            \"BLS12_381\" => Ok(Curve::Bls12_381),\n            \"GOLDILOCKS\" => Ok(Curve::Goldilocks),\n            _ => Err(anyhow!(\"failed to parse curve `{curve}`\")),\n        }\n    }\n}\n\n#[derive(Clone)]\npub struct UsefulConstants {\n    curve: Curve,\n    prime: BigInt,\n}\n\nimpl UsefulConstants {\n    pub fn new(curve: &Curve) -> UsefulConstants {\n        UsefulConstants { curve: curve.clone(), prime: curve.prime() }\n    }\n\n    /// Returns the used curve.\n    pub fn curve(&self) -> &Curve {\n        &self.curve\n    }\n\n    /// Returns the used prime.\n    pub fn prime(&self) -> &BigInt {\n        &self.prime\n    }\n\n    /// Returns the size in bits of the used prime.\n    pub fn prime_size(&self) -> usize {\n        self.prime.bits()\n    }\n}\n"
  },
  {
    "path": "program_structure/src/utils/environment.rs",
    "content": "use std::collections::HashMap;\nuse std::hash::Hash;\nuse std::marker::PhantomData;\n\npub trait VarInfo {}\npub trait SignalInfo {}\npub trait ComponentInfo {}\n\n#[derive(Clone)]\npub struct OnlyVars;\nimpl VarInfo for OnlyVars {}\n#[derive(Clone)]\npub struct OnlySignals;\nimpl SignalInfo for OnlySignals {}\n#[derive(Clone)]\npub struct OnlyComponents;\nimpl ComponentInfo for OnlyComponents {}\n#[derive(Clone)]\npub struct FullEnvironment;\nimpl VarInfo for FullEnvironment {}\nimpl SignalInfo for FullEnvironment {}\nimpl ComponentInfo for FullEnvironment {}\n\npub type VarEnvironment<VC> = RawEnvironment<OnlyVars, (), (), VC>;\npub type SignalEnvironment<SC> = RawEnvironment<OnlySignals, (), SC, ()>;\npub type ComponentEnvironment<CC> = RawEnvironment<OnlyComponents, CC, (), ()>;\npub type CircomEnvironment<CC, SC, VC> = RawEnvironment<FullEnvironment, CC, SC, VC>;\n\npub enum CircomEnvironmentError {\n    NonExistentSymbol,\n}\n\n#[derive(Clone)]\npub struct RawEnvironment<T, CC, SC, VC> {\n    components: HashMap<String, CC>,\n    inputs: HashMap<String, SC>,\n    outputs: HashMap<String, SC>,\n    intermediates: HashMap<String, SC>,\n    variables: Vec<VariableBlock<VC>>,\n    behaviour: PhantomData<T>,\n}\nimpl<T, CC, SC, VC> Default for RawEnvironment<T, CC, SC, VC> {\n    fn default() -> Self {\n        let variables = vec![VariableBlock::new()];\n        RawEnvironment {\n            components: HashMap::new(),\n            inputs: HashMap::new(),\n            outputs: HashMap::new(),\n            intermediates: HashMap::new(),\n            variables,\n            behaviour: PhantomData,\n        }\n    }\n}\nimpl<T, CC, SC, VC> RawEnvironment<T, CC, SC, VC>\nwhere\n    T: VarInfo + SignalInfo + ComponentInfo,\n{\n    pub fn has_symbol(&self, symbol: &str) -> bool {\n        self.has_signal(symbol) || self.has_component(symbol) || self.has_variable(symbol)\n    }\n}\nimpl<T, CC, SC, VC> RawEnvironment<T, CC, SC, VC> {\n    pub fn merge(\n        left: RawEnvironment<T, CC, SC, VC>,\n        right: RawEnvironment<T, CC, SC, VC>,\n        using: fn(VC, VC) -> VC,\n    ) -> RawEnvironment<T, CC, SC, VC> {\n        let mut components = left.components;\n        let mut inputs = left.inputs;\n        let mut outputs = left.outputs;\n        let mut intermediates = left.intermediates;\n        components.extend(right.components);\n        inputs.extend(right.inputs);\n        outputs.extend(right.outputs);\n        intermediates.extend(right.intermediates);\n        let mut variables_left = left.variables;\n        let mut variables_right = right.variables;\n        let mut variables = Vec::new();\n        while !variables_left.is_empty() && !variables_right.is_empty() {\n            let left_block = variables_left.pop().unwrap();\n            let right_block = variables_right.pop().unwrap();\n            let merged_blocks = VariableBlock::merge(left_block, right_block, using);\n            variables.push(merged_blocks);\n        }\n        variables.reverse();\n        RawEnvironment {\n            components,\n            inputs,\n            intermediates,\n            outputs,\n            variables,\n            behaviour: PhantomData,\n        }\n    }\n}\nimpl<T, CC, SC, VC> RawEnvironment<T, CC, SC, VC>\nwhere\n    T: VarInfo,\n{\n    fn block_with_variable_symbol(&self, symbol: &str) -> Option<&VariableBlock<VC>> {\n        let variables = &self.variables;\n        let mut act = variables.len();\n        while act > 0 {\n            if VariableBlock::contains_variable(&variables[act - 1], symbol) {\n                return Some(&variables[act - 1]);\n            }\n            act -= 1;\n        }\n        None\n    }\n    fn mut_block_with_variable_symbol(&mut self, symbol: &str) -> Option<&mut VariableBlock<VC>> {\n        let variables = &mut self.variables;\n        let mut act = variables.len();\n        while act > 0 {\n            if VariableBlock::contains_variable(&variables[act - 1], symbol) {\n                return Some(&mut variables[act - 1]);\n            }\n            act -= 1;\n        }\n        None\n    }\n    pub fn new() -> RawEnvironment<T, CC, SC, VC> {\n        RawEnvironment::default()\n    }\n    pub fn add_variable_block(&mut self) {\n        self.variables.push(VariableBlock::new());\n    }\n    pub fn remove_variable_block(&mut self) {\n        assert!(!self.variables.is_empty());\n        self.variables.pop();\n    }\n    pub fn add_variable(&mut self, variable_name: &str, content: VC) {\n        assert!(!self.variables.is_empty());\n        let last_block = self.variables.last_mut().unwrap();\n        last_block.add_variable(variable_name, content);\n    }\n    pub fn has_variable(&self, symbol: &str) -> bool {\n        self.block_with_variable_symbol(symbol).is_some()\n    }\n\n    pub fn get_variable(&self, symbol: &str) -> Option<&VC> {\n        let possible_block = self.block_with_variable_symbol(symbol);\n        if let Some(block) = possible_block {\n            Some(block.get_variable(symbol))\n        } else {\n            None\n        }\n    }\n    pub fn get_mut_variable(&mut self, symbol: &str) -> Option<&mut VC> {\n        let possible_block = self.mut_block_with_variable_symbol(symbol);\n        if let Some(block) = possible_block {\n            Some(block.get_mut_variable(symbol))\n        } else {\n            None\n        }\n    }\n    pub fn get_variable_res(&self, symbol: &str) -> Result<&VC, CircomEnvironmentError> {\n        let possible_block = self.block_with_variable_symbol(symbol);\n        if let Some(block) = possible_block {\n            Ok(block.get_variable(symbol))\n        } else {\n            Err(CircomEnvironmentError::NonExistentSymbol)\n        }\n    }\n    pub fn remove_variable(&mut self, symbol: &str) {\n        let possible_block = self.mut_block_with_variable_symbol(symbol);\n        if let Some(block) = possible_block {\n            block.remove_variable(symbol)\n        }\n    }\n    pub fn get_variable_or_break(&self, symbol: &str, file: &str, line: u32) -> &VC {\n        assert!(self.has_variable(symbol), \"Method call in file {file} line {line}\");\n        if let Ok(v) = self.get_variable_res(symbol) {\n            v\n        } else {\n            unreachable!();\n        }\n    }\n    pub fn get_mut_variable_mut(\n        &mut self,\n        symbol: &str,\n    ) -> Result<&mut VC, CircomEnvironmentError> {\n        let possible_block = self.mut_block_with_variable_symbol(symbol);\n        if let Some(block) = possible_block {\n            Ok(block.get_mut_variable(symbol))\n        } else {\n            Err(CircomEnvironmentError::NonExistentSymbol)\n        }\n    }\n    pub fn get_mut_variable_or_break(&mut self, symbol: &str, file: &str, line: u32) -> &mut VC {\n        assert!(self.has_variable(symbol), \"Method call in file {file} line {line}\");\n        if let Ok(v) = self.get_mut_variable_mut(symbol) {\n            v\n        } else {\n            unreachable!();\n        }\n    }\n    pub fn variable_iter(&self) -> impl Iterator<Item = (&String, &VC)> {\n        self.variables.iter().flat_map(|block| block.iter())\n    }\n}\n\nimpl<T, CC, SC, VC> RawEnvironment<T, CC, SC, VC>\nwhere\n    T: ComponentInfo,\n{\n    pub fn add_component(&mut self, component_name: &str, content: CC) {\n        self.components.insert(component_name.to_string(), content);\n    }\n    pub fn remove_component(&mut self, component_name: &str) {\n        self.components.remove(component_name);\n    }\n    pub fn has_component(&self, symbol: &str) -> bool {\n        self.components.contains_key(symbol)\n    }\n    pub fn get_component(&self, symbol: &str) -> Option<&CC> {\n        self.components.get(symbol)\n    }\n    pub fn get_mut_component(&mut self, symbol: &str) -> Option<&mut CC> {\n        self.components.get_mut(symbol)\n    }\n    pub fn get_component_res(&self, symbol: &str) -> Result<&CC, CircomEnvironmentError> {\n        self.components.get(symbol).ok_or(CircomEnvironmentError::NonExistentSymbol)\n    }\n    pub fn get_component_or_break(&self, symbol: &str, file: &str, line: u32) -> &CC {\n        assert!(self.has_component(symbol), \"Method call in file {file} line {line}\");\n        self.components.get(symbol).unwrap()\n    }\n    pub fn get_mut_component_res(\n        &mut self,\n        symbol: &str,\n    ) -> Result<&mut CC, CircomEnvironmentError> {\n        self.components.get_mut(symbol).ok_or(CircomEnvironmentError::NonExistentSymbol)\n    }\n    pub fn get_mut_component_or_break(&mut self, symbol: &str, file: &str, line: u32) -> &mut CC {\n        assert!(self.has_component(symbol), \"Method call in file {file} line {line}\");\n        self.components.get_mut(symbol).unwrap()\n    }\n}\n\nimpl<T, CC, SC, VC> RawEnvironment<T, CC, SC, VC>\nwhere\n    T: SignalInfo,\n{\n    pub fn add_input(&mut self, input_name: &str, content: SC) {\n        self.inputs.insert(input_name.to_string(), content);\n    }\n    pub fn remove_input(&mut self, input_name: &str) {\n        self.inputs.remove(input_name);\n    }\n    pub fn add_output(&mut self, output_name: &str, content: SC) {\n        self.outputs.insert(output_name.to_string(), content);\n    }\n    pub fn remove_output(&mut self, output_name: &str) {\n        self.outputs.remove(output_name);\n    }\n    pub fn add_intermediate(&mut self, intermediate_name: &str, content: SC) {\n        self.intermediates.insert(intermediate_name.to_string(), content);\n    }\n    pub fn remove_intermediate(&mut self, intermediate_name: &str) {\n        self.intermediates.remove(intermediate_name);\n    }\n    pub fn has_input(&self, symbol: &str) -> bool {\n        self.inputs.contains_key(symbol)\n    }\n    pub fn has_output(&self, symbol: &str) -> bool {\n        self.outputs.contains_key(symbol)\n    }\n    pub fn has_intermediate(&self, symbol: &str) -> bool {\n        self.intermediates.contains_key(symbol)\n    }\n    pub fn has_signal(&self, symbol: &str) -> bool {\n        self.has_input(symbol) || self.has_output(symbol) || self.has_intermediate(symbol)\n    }\n    pub fn get_input(&self, symbol: &str) -> Option<&SC> {\n        self.inputs.get(symbol)\n    }\n    pub fn get_mut_input(&mut self, symbol: &str) -> Option<&mut SC> {\n        self.inputs.get_mut(symbol)\n    }\n    pub fn get_input_res(&self, symbol: &str) -> Result<&SC, CircomEnvironmentError> {\n        self.inputs.get(symbol).ok_or(CircomEnvironmentError::NonExistentSymbol)\n    }\n    pub fn get_input_or_break(&self, symbol: &str, file: &str, line: u32) -> &SC {\n        assert!(self.has_input(symbol), \"Method call in file {file} line {line}\");\n        self.inputs.get(symbol).unwrap()\n    }\n    pub fn get_mut_input_res(&mut self, symbol: &str) -> Result<&mut SC, CircomEnvironmentError> {\n        self.inputs.get_mut(symbol).ok_or(CircomEnvironmentError::NonExistentSymbol)\n    }\n    pub fn get_mut_input_or_break(&mut self, symbol: &str, file: &str, line: u32) -> &mut SC {\n        assert!(self.has_input(symbol), \"Method call in file {file} line {line}\");\n        self.inputs.get_mut(symbol).unwrap()\n    }\n\n    pub fn get_output(&self, symbol: &str) -> Option<&SC> {\n        self.outputs.get(symbol)\n    }\n    pub fn get_mut_output(&mut self, symbol: &str) -> Option<&mut SC> {\n        self.outputs.get_mut(symbol)\n    }\n    pub fn get_output_res(&self, symbol: &str) -> Result<&SC, CircomEnvironmentError> {\n        self.outputs.get(symbol).ok_or(CircomEnvironmentError::NonExistentSymbol)\n    }\n    pub fn get_output_or_break(&self, symbol: &str, file: &str, line: u32) -> &SC {\n        assert!(self.has_output(symbol), \"Method call in file {file} line {line}\");\n        self.outputs.get(symbol).unwrap()\n    }\n    pub fn get_mut_output_res(&mut self, symbol: &str) -> Result<&mut SC, CircomEnvironmentError> {\n        self.outputs.get_mut(symbol).ok_or(CircomEnvironmentError::NonExistentSymbol)\n    }\n    pub fn get_mut_output_or_break(&mut self, symbol: &str, file: &str, line: u32) -> &mut SC {\n        assert!(self.has_output(symbol), \"Method call in file {file} line {line}\");\n        self.outputs.get_mut(symbol).unwrap()\n    }\n\n    pub fn get_intermediate(&self, symbol: &str) -> Option<&SC> {\n        self.intermediates.get(symbol)\n    }\n    pub fn get_mut_intermediate(&mut self, symbol: &str) -> Option<&mut SC> {\n        self.intermediates.get_mut(symbol)\n    }\n    pub fn get_intermediate_res(&self, symbol: &str) -> Result<&SC, CircomEnvironmentError> {\n        self.intermediates.get(symbol).ok_or(CircomEnvironmentError::NonExistentSymbol)\n    }\n    pub fn get_intermediate_or_break(&self, symbol: &str, file: &str, line: u32) -> &SC {\n        assert!(self.has_intermediate(symbol), \"Method call in file {file} line {line}\");\n        self.intermediates.get(symbol).unwrap()\n    }\n    pub fn get_mut_intermediate_res(\n        &mut self,\n        symbol: &str,\n    ) -> Result<&mut SC, CircomEnvironmentError> {\n        self.intermediates.get_mut(symbol).ok_or(CircomEnvironmentError::NonExistentSymbol)\n    }\n    pub fn get_mut_intermediate_or_break(\n        &mut self,\n        symbol: &str,\n        file: &str,\n        line: u32,\n    ) -> &mut SC {\n        assert!(self.has_intermediate(symbol), \"Method call in file {file} line {line}\");\n        self.intermediates.get_mut(symbol).unwrap()\n    }\n\n    pub fn get_signal(&self, symbol: &str) -> Option<&SC> {\n        if self.has_input(symbol) {\n            self.get_input(symbol)\n        } else if self.has_output(symbol) {\n            self.get_output(symbol)\n        } else if self.has_intermediate(symbol) {\n            self.get_intermediate(symbol)\n        } else {\n            None\n        }\n    }\n    pub fn get_mut_signal(&mut self, symbol: &str) -> Option<&mut SC> {\n        if self.has_input(symbol) {\n            self.get_mut_input(symbol)\n        } else if self.has_output(symbol) {\n            self.get_mut_output(symbol)\n        } else if self.has_intermediate(symbol) {\n            self.get_mut_intermediate(symbol)\n        } else {\n            None\n        }\n    }\n    pub fn get_signal_res(&self, symbol: &str) -> Result<&SC, CircomEnvironmentError> {\n        if self.has_input(symbol) {\n            self.get_input_res(symbol)\n        } else if self.has_output(symbol) {\n            self.get_output_res(symbol)\n        } else if self.has_intermediate(symbol) {\n            self.get_intermediate_res(symbol)\n        } else {\n            Err(CircomEnvironmentError::NonExistentSymbol)\n        }\n    }\n    pub fn get_signal_or_break(&self, symbol: &str, file: &str, line: u32) -> &SC {\n        assert!(self.has_signal(symbol), \"Method call in file {file} line {line}\");\n        if let Ok(v) = self.get_signal_res(symbol) {\n            v\n        } else {\n            unreachable!();\n        }\n    }\n    pub fn get_mut_signal_res(&mut self, symbol: &str) -> Result<&mut SC, CircomEnvironmentError> {\n        if self.has_input(symbol) {\n            self.get_mut_input_res(symbol)\n        } else if self.has_output(symbol) {\n            self.get_mut_output_res(symbol)\n        } else if self.has_intermediate(symbol) {\n            self.get_mut_intermediate_res(symbol)\n        } else {\n            Err(CircomEnvironmentError::NonExistentSymbol)\n        }\n    }\n    pub fn get_mut_signal_or_break(&mut self, symbol: &str, file: &str, line: u32) -> &mut SC {\n        assert!(self.has_signal(symbol), \"Method call in file {file} line {line}\");\n        if let Ok(v) = self.get_mut_signal_res(symbol) {\n            v\n        } else {\n            unreachable!();\n        }\n    }\n}\n\n#[derive(Clone)]\nstruct VariableBlock<VC> {\n    variables: HashMap<String, VC>,\n}\nimpl<VC> Default for VariableBlock<VC> {\n    fn default() -> Self {\n        VariableBlock { variables: HashMap::new() }\n    }\n}\nimpl<VC> VariableBlock<VC> {\n    pub fn new() -> VariableBlock<VC> {\n        VariableBlock::default()\n    }\n    pub fn add_variable(&mut self, symbol: &str, content: VC) {\n        self.variables.insert(symbol.to_string(), content);\n    }\n    pub fn remove_variable(&mut self, symbol: &str) {\n        self.variables.remove(symbol);\n    }\n    pub fn contains_variable(&self, symbol: &str) -> bool {\n        self.variables.contains_key(symbol)\n    }\n    pub fn get_variable(&self, symbol: &str) -> &VC {\n        assert!(self.contains_variable(symbol));\n        self.variables.get(symbol).unwrap()\n    }\n    pub fn get_mut_variable(&mut self, symbol: &str) -> &mut VC {\n        assert!(self.contains_variable(symbol));\n        self.variables.get_mut(symbol).unwrap()\n    }\n    pub fn iter(&self) -> impl Iterator<Item = (&String, &VC)> {\n        self.variables.iter()\n    }\n    pub fn merge(\n        left: VariableBlock<VC>,\n        right: VariableBlock<VC>,\n        using: fn(VC, VC) -> VC,\n    ) -> VariableBlock<VC> {\n        let left_block = left.variables;\n        let right_block = right.variables;\n        let result_block = hashmap_union(left_block, right_block, using);\n        VariableBlock { variables: result_block }\n    }\n}\n\nfn hashmap_union<K, V>(\n    l: HashMap<K, V>,\n    mut r: HashMap<K, V>,\n    merge_function: fn(V, V) -> V,\n) -> HashMap<K, V>\nwhere\n    K: Hash + Eq,\n{\n    let mut result = HashMap::new();\n    for (k, v) in l {\n        if let Some(r_v) = r.remove(&k) {\n            result.insert(k, merge_function(v, r_v));\n        } else {\n            result.insert(k, v);\n        }\n    }\n    for (k, v) in r {\n        result.entry(k).or_insert(v);\n    }\n    result\n}\n"
  },
  {
    "path": "program_structure/src/utils/mod.rs",
    "content": "pub mod constants;\npub mod environment;\npub mod nonempty_vec;\npub mod writers;\npub mod sarif_conversion;\n"
  },
  {
    "path": "program_structure/src/utils/nonempty_vec.rs",
    "content": "use anyhow::{anyhow, Error};\nuse std::convert::TryFrom;\nuse std::ops::{Index, IndexMut};\n\n/// A vector type which is guaranteed to be non-empty.\n///\n/// New instances must be created with an initial element to ensure that the\n/// vector is non-empty. This means that the methods `first` and `last` always\n/// produce an element of type `T`.\n///\n/// ```\n/// # use circomspect_program_structure::nonempty_vec::NonEmptyVec;\n///\n/// let v = NonEmptyVec::new(1);\n/// assert_eq!(*v.first(), 1);\n/// assert_eq!(*v.last(), 1);\n/// ```\n///\n/// It is possible to `push` new elements into the vector, but `pop` will return\n/// `None` if there is only one element left to ensure that the vector is always\n/// nonempty.\n///\n/// ```\n/// # use circomspect_program_structure::nonempty_vec::NonEmptyVec;\n///\n/// let mut v = NonEmptyVec::new(1);\n/// v.push(2);\n/// assert_eq!(v.pop(), Some(2));\n/// assert_eq!(v.pop(), None);\n/// ```\n#[derive(Clone, PartialEq, Eq)]\npub struct NonEmptyVec<T> {\n    head: T,\n    tail: Vec<T>,\n}\n\nimpl<T> NonEmptyVec<T> {\n    pub fn new(x: T) -> NonEmptyVec<T> {\n        NonEmptyVec { head: x, tail: Vec::new() }\n    }\n\n    pub fn first(&self) -> &T {\n        &self.head\n    }\n\n    pub fn first_mut(&mut self) -> &mut T {\n        &mut self.head\n    }\n\n    /// Returns a reference to the last element.\n    pub fn last(&self) -> &T {\n        match self.tail.last() {\n            Some(x) => x,\n            None => &self.head,\n        }\n    }\n\n    /// Returns a mutable reference to the last element.\n    pub fn last_mut(&mut self) -> &mut T {\n        match self.tail.last_mut() {\n            Some(x) => x,\n            None => &mut self.head,\n        }\n    }\n\n    /// Append an element to the vector.\n    pub fn push(&mut self, x: T) {\n        self.tail.push(x);\n    }\n\n    /// Pops the last element of the vector.\n    ///\n    /// This method will return `None` when there is one element left in the\n    /// vector to ensure that the vector remains non-empty.\n    ///\n    /// ```\n    /// # use circomspect_program_structure::nonempty_vec::NonEmptyVec;\n    ///\n    /// let mut v = NonEmptyVec::new(1);\n    /// v.push(2);\n    /// assert_eq!(v.pop(), Some(2));\n    /// assert_eq!(v.pop(), None);\n    /// ```\n    pub fn pop(&mut self) -> Option<T> {\n        self.tail.pop()\n    }\n\n    /// Returns the length of the vector.\n    ///\n    /// ```\n    /// # use circomspect_program_structure::nonempty_vec::NonEmptyVec;\n    ///\n    /// let mut v = NonEmptyVec::new(1);\n    /// v.push(2);\n    /// v.push(3);\n    /// assert_eq!(v.len(), 3);\n    /// ```\n    pub fn len(&self) -> usize {\n        self.tail.len() + 1\n    }\n\n    /// Always returns false.\n    pub fn is_empty(&self) -> bool {\n        false\n    }\n\n    /// Returns an iterator over the vector.\n    pub fn iter(&self) -> NonEmptyIter<'_, T> {\n        NonEmptyIter::new(self)\n    }\n}\n\n/// Allows for constructions on the form `for t in ts`.\nimpl<'a, T> IntoIterator for &'a NonEmptyVec<T> {\n    type Item = &'a T;\n    type IntoIter = NonEmptyIter<'a, T>;\n\n    fn into_iter(self) -> Self::IntoIter {\n        NonEmptyIter::new(self)\n    }\n}\n\n/// An iterator over a non-empty vector.\n///\n/// ```\n/// # use circomspect_program_structure::nonempty_vec::NonEmptyVec;\n/// # use std::convert::TryFrom;\n/// let v = NonEmptyVec::try_from(&[1, 2, 3]).unwrap();\n///\n/// let mut iter = v.iter();\n/// assert_eq!(iter.next(), Some(&1));\n/// assert_eq!(iter.next(), Some(&2));\n/// assert_eq!(iter.next(), Some(&3));\n/// assert_eq!(iter.next(), None);\n/// ```\npub struct NonEmptyIter<'a, T> {\n    index: usize,\n    vec: &'a NonEmptyVec<T>,\n}\n\nimpl<'a, T> NonEmptyIter<'a, T> {\n    fn new(vec: &'a NonEmptyVec<T>) -> NonEmptyIter<'a, T> {\n        NonEmptyIter { index: 0, vec }\n    }\n}\n\nimpl<'a, T> Iterator for NonEmptyIter<'a, T> {\n    type Item = &'a T;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        let x = if self.index == 0 {\n            Some(&self.vec.head)\n        } else {\n            // self.index > 0 here so the subtraction cannot underflow.\n            self.vec.tail.get(self.index - 1)\n        };\n        self.index += 1;\n        x\n    }\n}\n\nimpl<T> Index<usize> for NonEmptyVec<T> {\n    type Output = T;\n\n    fn index(&self, index: usize) -> &Self::Output {\n        match index {\n            0 => &self.head,\n            n => &self.tail[n - 1],\n        }\n    }\n}\n\nimpl<T> Index<&usize> for NonEmptyVec<T> {\n    type Output = T;\n\n    fn index(&self, index: &usize) -> &Self::Output {\n        match index {\n            0 => &self.head,\n            n => &self.tail[n - 1],\n        }\n    }\n}\n\nimpl<T> IndexMut<usize> for NonEmptyVec<T> {\n    fn index_mut(&mut self, index: usize) -> &mut Self::Output {\n        match index {\n            0 => &mut self.head,\n            n => &mut self.tail[n - 1],\n        }\n    }\n}\n\nimpl<T> IndexMut<&usize> for NonEmptyVec<T> {\n    fn index_mut(&mut self, index: &usize) -> &mut Self::Output {\n        match index {\n            0 => &mut self.head,\n            n => &mut self.tail[n - 1],\n        }\n    }\n}\n\nimpl<T> From<NonEmptyVec<T>> for Vec<T> {\n    fn from(xs: NonEmptyVec<T>) -> Vec<T> {\n        let mut res = Vec::with_capacity(xs.len());\n        res.push(xs.head);\n        res.extend(xs.tail);\n        res\n    }\n}\n\nimpl<T: Clone> From<&NonEmptyVec<T>> for Vec<T> {\n    fn from(xs: &NonEmptyVec<T>) -> Vec<T> {\n        xs.iter().cloned().collect()\n    }\n}\n\nimpl<T> TryFrom<Vec<T>> for NonEmptyVec<T> {\n    type Error = Error;\n\n    fn try_from(mut xs: Vec<T>) -> Result<NonEmptyVec<T>, Error> {\n        if let Some(x) = xs.pop() {\n            Ok(NonEmptyVec { head: x, tail: xs })\n        } else {\n            Err(anyhow!(\"cannot create a non-empty vector from an empty vector\"))\n        }\n    }\n}\n\nimpl<T: Clone> TryFrom<&Vec<T>> for NonEmptyVec<T> {\n    type Error = Error;\n\n    fn try_from(xs: &Vec<T>) -> Result<NonEmptyVec<T>, Error> {\n        if let Some(x) = xs.first() {\n            Ok(NonEmptyVec { head: x.clone(), tail: xs[1..].to_vec() })\n        } else {\n            Err(anyhow!(\"cannot create a non-empty vector from an empty vector\"))\n        }\n    }\n}\n\nimpl<T: Clone> TryFrom<&[T]> for NonEmptyVec<T> {\n    type Error = Error;\n\n    fn try_from(xs: &[T]) -> Result<NonEmptyVec<T>, Error> {\n        if let Some(x) = xs.first() {\n            Ok(NonEmptyVec { head: x.clone(), tail: xs[1..].to_vec() })\n        } else {\n            Err(anyhow!(\"cannot create a non-empty vector from an empty vector\"))\n        }\n    }\n}\n\nimpl<T: Clone, const N: usize> TryFrom<&[T; N]> for NonEmptyVec<T> {\n    type Error = Error;\n\n    fn try_from(xs: &[T; N]) -> Result<NonEmptyVec<T>, Error> {\n        if let Some(x) = xs.first() {\n            Ok(NonEmptyVec { head: x.clone(), tail: xs[1..].to_vec() })\n        } else {\n            Err(anyhow!(\"cannot create a non-empty vector from an empty vector\"))\n        }\n    }\n}\n"
  },
  {
    "path": "program_structure/src/utils/sarif_conversion.rs",
    "content": "use codespan_reporting::files::Files;\nuse log::{debug, trace};\nuse serde_sarif::sarif;\nuse std::collections::HashSet;\nuse std::fmt;\nuse std::ops::Range;\nuse std::path::PathBuf;\nuse thiserror::Error;\n\nuse crate::report::{Report, ReportCollection, ReportLabel};\nuse crate::file_definition::{FileID, FileLibrary};\n\n// This is the Sarif file format version, not the tool version.\nconst SARIF_VERSION: &str = \"2.1.0\";\nconst DRIVER_NAME: &str = \"Circomspect\";\nconst ORGANIZATION: &str = \"Trail of Bits\";\nconst DOWNLOAD_URI: &str = \"https://github.com/trailofbits/circomspect\";\n\n/// A trait for objects that can be converted into a Sarif artifact.\npub trait ToSarif {\n    type Sarif;\n    type Error;\n\n    /// Converts the object to the corresponding Sarif artifact.\n    fn to_sarif(&self, files: &FileLibrary) -> Result<Self::Sarif, Self::Error>;\n}\n\nimpl ToSarif for ReportCollection {\n    type Sarif = sarif::Sarif;\n    type Error = SarifError;\n\n    fn to_sarif(&self, files: &FileLibrary) -> Result<Self::Sarif, Self::Error> {\n        debug!(\"converting report collection to Sarif-format\");\n        // Build reporting descriptors.\n        trace!(\"building reporting descriptors\");\n        let rules = self\n            .iter()\n            .map(|report| (report.name(), report.id()))\n            .collect::<HashSet<_>>()\n            .iter()\n            .map(|(name, id)| {\n                sarif::ReportingDescriptorBuilder::default().name(name).id(id).build()\n            })\n            .collect::<Result<Vec<_>, _>>()\n            .map_err(SarifError::from)?;\n        // Build tool.\n        //\n        // TODO: Should include primary package version.\n        trace!(\"building tool\");\n        let driver = sarif::ToolComponentBuilder::default()\n            .name(DRIVER_NAME)\n            .organization(ORGANIZATION)\n            .download_uri(DOWNLOAD_URI)\n            .rules(rules)\n            .build()?;\n        let tool = sarif::ToolBuilder::default().driver(driver).build()?;\n        // Build run.\n        trace!(\"building run\");\n        let results =\n            self.iter().map(|report| report.to_sarif(files)).collect::<SarifResult<Vec<_>>>()?;\n        let run = sarif::RunBuilder::default().tool(tool).results(results).build()?;\n        // Build main object.\n        trace!(\"building main Sarif object\");\n        let sarif = sarif::SarifBuilder::default().runs(vec![run]).version(SARIF_VERSION).build();\n        sarif.map_err(SarifError::from)\n    }\n}\n\nimpl ToSarif for Report {\n    type Sarif = sarif::Result;\n    type Error = SarifError;\n\n    fn to_sarif(&self, files: &FileLibrary) -> SarifResult<sarif::Result> {\n        let level = self.category().to_level();\n        let rule_id = self.id();\n        // Build message.\n        trace!(\"building message\");\n        let message = sarif::MessageBuilder::default().text(self.message()).build()?;\n        // Build primary and secondary locations.\n        trace!(\"building locations\");\n        let locations = self\n            .primary()\n            .iter()\n            .map(|label| label.to_sarif(files))\n            .collect::<SarifResult<Vec<_>>>()?;\n        let related_locations = self\n            .secondary()\n            .iter()\n            .map(|label| label.to_sarif(files))\n            .collect::<SarifResult<Vec<_>>>()?;\n        // Build reporting descriptor reference.\n        let rule = sarif::ReportingDescriptorReferenceBuilder::default()\n            .id(&rule_id)\n            .build()\n            .map_err(SarifError::from)?;\n        // Build result.\n        trace!(\"building result\");\n        sarif::ResultBuilder::default()\n            .level(level)\n            .message(message)\n            .rule_id(rule_id)\n            .rule(rule)\n            .locations(locations)\n            .related_locations(related_locations)\n            .build()\n            .map_err(SarifError::from)\n    }\n}\n\nimpl ToSarif for ReportLabel {\n    type Sarif = sarif::Location;\n    type Error = SarifError;\n\n    fn to_sarif(&self, files: &FileLibrary) -> SarifResult<sarif::Location> {\n        // Build artifact location.\n        trace!(\"building artifact location\");\n        let file_uri = self.file_id.to_uri(files)?;\n        let artifact_location = sarif::ArtifactLocationBuilder::default().uri(file_uri).build()?;\n        // Build region.\n        trace!(\"building region\");\n        assert!(self.range.start <= self.range.end);\n        let start = files\n            .to_storage()\n            .location(self.file_id, self.range.start)\n            .map_err(|_| SarifError::UnknownLocation(self.file_id, self.range.clone()))?;\n        let end = files\n            .to_storage()\n            .location(self.file_id, self.range.end)\n            .map_err(|_| SarifError::UnknownLocation(self.file_id, self.range.clone()))?;\n        let region = sarif::RegionBuilder::default()\n            .start_line(start.line_number as i64)\n            .start_column(start.column_number as i64)\n            .end_line(end.line_number as i64)\n            .end_column(end.column_number as i64)\n            .build()?;\n        // Build physical location.\n        trace!(\"building physical location\");\n        let physical_location = sarif::PhysicalLocationBuilder::default()\n            .artifact_location(artifact_location)\n            .region(region)\n            .build()?;\n        // Build message.\n        trace!(\"building message\");\n        let message = sarif::MessageBuilder::default().text(self.message.clone()).build()?;\n        // Build location.\n        trace!(\"building location\");\n        sarif::LocationBuilder::default()\n            .physical_location(physical_location)\n            .id(0)\n            .message(message)\n            .build()\n            .map_err(SarifError::from)\n    }\n}\n\ntrait ToUri {\n    type Error;\n    fn to_uri(&self, files: &FileLibrary) -> Result<String, Self::Error>;\n}\n\nimpl ToUri for FileID {\n    type Error = SarifError;\n\n    fn to_uri(&self, files: &FileLibrary) -> Result<String, SarifError> {\n        let path: PathBuf = files\n            .to_storage()\n            .get(*self)\n            .map_err(|_| SarifError::UnknownFile(*self))?\n            .name()\n            .replace('\"', \"\")\n            .into();\n        // This path already comes from an UTF-8 string so it is ok to unwrap here.\n        Ok(format!(\"file://{}\", path.to_str().unwrap()))\n    }\n}\n\n#[derive(Error, Debug)]\npub enum SarifError {\n    InvalidReportingDescriptorReference(#[from] sarif::ReportingDescriptorReferenceBuilderError),\n    InvalidReportingDescriptor(#[from] sarif::ReportingDescriptorBuilderError),\n    InvalidPhysicalLocationError(#[from] sarif::PhysicalLocationBuilderError),\n    InvalidArtifactLocation(#[from] sarif::ArtifactLocationBuilderError),\n    InvalidToolComponent(#[from] sarif::ToolComponentBuilderError),\n    InvalidLocation(#[from] sarif::LocationBuilderError),\n    InvalidMessage(#[from] sarif::MessageBuilderError),\n    InvalidRegion(#[from] sarif::RegionBuilderError),\n    InvalidResult(#[from] sarif::ResultBuilderError),\n    InvalidRun(#[from] sarif::RunBuilderError),\n    InvalidSarif(#[from] sarif::SarifBuilderError),\n    InvalidTool(#[from] sarif::ToolBuilderError),\n    InvalidFix(#[from] sarif::FixBuilderError),\n    UnknownLocation(FileID, Range<usize>),\n    UnknownFile(FileID),\n}\n\ntype SarifResult<T> = Result<T, SarifError>;\n\nimpl fmt::Display for SarifError {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        write!(f, \"failed to convert analysis results to Sarif format\")\n    }\n}\n"
  },
  {
    "path": "program_structure/src/utils/writers.rs",
    "content": "use anyhow;\nuse anyhow::Context;\nuse log::{info, warn};\nuse std::fmt::Display;\nuse std::fs::File;\nuse std::io::Write;\nuse std::path::{PathBuf, Path};\nuse codespan_reporting::term;\nuse termcolor::{StandardStream, ColorChoice, WriteColor, ColorSpec, Color};\n\nuse crate::sarif_conversion::ToSarif;\nuse crate::{\n    program_library::report::{Report, ReportCollection},\n    file_definition::FileLibrary,\n};\n\npub trait ReportFilter {\n    /// Returns true if the report should be included.\n    fn filter(&self, report: &Report) -> bool;\n}\n\nimpl<F: Fn(&Report) -> bool> ReportFilter for F {\n    fn filter(&self, report: &Report) -> bool {\n        self(report)\n    }\n}\n\npub trait ReportWriter {\n    /// Filter and write the given reports. Returns the number of reports written.\n    fn write_reports(&mut self, reports: &[Report], file_library: &FileLibrary) -> usize;\n\n    /// Filter and write a single report. Returns the number of reports written (0 or 1).\n    fn write_report(&mut self, report: Report, file_library: &FileLibrary) -> usize {\n        self.write_reports(&[report], file_library)\n    }\n\n    /// Returns the number of reports written.\n    #[must_use]\n    fn reports_written(&self) -> usize;\n}\n\npub trait LogWriter {\n    fn write_messages<D: Display>(&mut self, messages: &[D]);\n\n    fn write_message<D: Display>(&mut self, message: D) {\n        self.write_messages(&[message]);\n    }\n}\n\npub struct StdoutWriter {\n    verbose: bool,\n    written: usize,\n    writer: StandardStream,\n    filters: Vec<Box<dyn ReportFilter>>,\n}\n\nimpl StdoutWriter {\n    pub fn new(verbose: bool) -> StdoutWriter {\n        let writer = if atty::is(atty::Stream::Stdout) {\n            StandardStream::stdout(ColorChoice::Always)\n        } else {\n            StandardStream::stdout(ColorChoice::Never)\n        };\n        StdoutWriter { verbose, written: 0, writer, filters: Vec::new() }\n    }\n\n    pub fn add_filter(mut self, filter: impl ReportFilter + 'static) -> StdoutWriter {\n        self.filters.push(Box::new(filter));\n        self\n    }\n\n    fn filter(&self, reports: &[Report]) -> ReportCollection {\n        reports\n            .iter()\n            .filter(|report| self.filters.iter().all(|f| f.filter(report)))\n            .cloned()\n            .collect()\n    }\n}\n\nimpl LogWriter for StdoutWriter {\n    fn write_messages<D: Display>(&mut self, messages: &[D]) {\n        let mut spec = ColorSpec::new();\n        spec.set_fg(Some(Color::Green));\n\n        let write_impl = |message: &D| {\n            let mut writer = self.writer.lock();\n            writer.set_color(&spec)?;\n            write!(&mut writer, \"circomspect\")?;\n            writer.reset()?;\n            writeln!(&mut writer, \": {message}\")\n        };\n        for message in messages {\n            write_impl(message).expect(\"failed to write log messages\")\n        }\n    }\n}\n\nimpl ReportWriter for StdoutWriter {\n    fn write_reports(&mut self, reports: &[Report], file_library: &FileLibrary) -> usize {\n        let reports = self.filter(reports);\n\n        let mut config = term::Config::default();\n        let mut diagnostics = Vec::new();\n        let files = file_library.to_storage();\n        for report in reports.iter() {\n            diagnostics.push(report.to_diagnostic(self.verbose));\n        }\n        config.styles.header_help.set_intense(false);\n        config.styles.header_error.set_intense(false);\n        config.styles.header_warning.set_intense(false);\n        for diagnostic in diagnostics.iter() {\n            term::emit(&mut self.writer.lock(), &config, files, diagnostic)\n                .expect(\"failed to write reports\");\n        }\n\n        self.written += reports.len();\n        reports.len()\n    }\n\n    /// Returns the number of reports written.\n    fn reports_written(&self) -> usize {\n        self.written\n    }\n}\n\n/// A `StdoutWriter` that caches all reports.\npub struct CachedStdoutWriter {\n    writer: StdoutWriter,\n    reports: ReportCollection,\n}\n\nimpl CachedStdoutWriter {\n    pub fn new(verbose: bool) -> CachedStdoutWriter {\n        CachedStdoutWriter { writer: StdoutWriter::new(verbose), reports: ReportCollection::new() }\n    }\n\n    pub fn reports(&self) -> &ReportCollection {\n        &self.reports\n    }\n\n    pub fn add_filter(mut self, filter: impl ReportFilter + 'static) -> CachedStdoutWriter {\n        self.writer.filters.push(Box::new(filter));\n        self\n    }\n}\n\nimpl LogWriter for CachedStdoutWriter {\n    fn write_messages<D: Display>(&mut self, messages: &[D]) {\n        self.writer.write_messages(messages)\n    }\n}\n\nimpl ReportWriter for CachedStdoutWriter {\n    fn write_reports(&mut self, reports: &[Report], file_library: &FileLibrary) -> usize {\n        self.reports.extend(reports.iter().cloned());\n        self.writer.write_reports(reports, file_library)\n    }\n\n    fn reports_written(&self) -> usize {\n        self.writer.reports_written()\n    }\n}\n\n#[derive(Default)]\npub struct SarifWriter {\n    sarif_file: PathBuf,\n    written: usize,\n    filters: Vec<Box<dyn ReportFilter>>,\n}\n\nimpl SarifWriter {\n    pub fn new(sarif_file: &Path) -> SarifWriter {\n        SarifWriter { sarif_file: sarif_file.to_owned(), ..Default::default() }\n    }\n\n    pub fn add_filter(mut self, filter: impl ReportFilter + 'static) -> SarifWriter {\n        self.filters.push(Box::new(filter));\n        self\n    }\n\n    fn filter(&self, reports: &[Report]) -> ReportCollection {\n        reports\n            .iter()\n            .filter(|report| self.filters.iter().all(|f| f.filter(report)))\n            .cloned()\n            .collect()\n    }\n\n    fn serialize_reports(\n        &self,\n        reports: &ReportCollection,\n        file_library: &FileLibrary,\n    ) -> anyhow::Result<()> {\n        let sarif =\n            reports.to_sarif(file_library).context(\"failed to convert reports to Sarif format\")?;\n        let json = serde_json::to_string_pretty(&sarif)?;\n        let mut sarif_file = File::create(&self.sarif_file)?;\n        writeln!(sarif_file, \"{}\", &json)\n            .with_context(|| format!(\"could not write to {}\", self.sarif_file.display()))?;\n        Ok(())\n    }\n}\n\nimpl ReportWriter for SarifWriter {\n    fn write_reports(&mut self, reports: &[Report], file_library: &FileLibrary) -> usize {\n        let reports = self.filter(reports);\n        match self.serialize_reports(&reports, file_library) {\n            Ok(()) => {\n                info!(\"reports written to `{}`\", self.sarif_file.display());\n                self.written += reports.len();\n                reports.len()\n            }\n            Err(_) => {\n                warn!(\"failed to write reports to `{}`\", self.sarif_file.display());\n                0\n            }\n        }\n    }\n\n    fn reports_written(&self) -> usize {\n        self.written\n    }\n}\n"
  },
  {
    "path": "program_structure_tests/Cargo.toml",
    "content": "[package]\nname = \"circomspect-program-structure-tests\"\nversion = \"0.8.0\"\nedition = \"2021\"\nrust-version = \"1.65\"\n\n[dependencies]\nparser = { package = \"circomspect-parser\", version = \"2.1.3\", path = \"../parser\" }\nprogram_structure = { package = \"circomspect-program-structure\", version = \"2.1.3\", path = \"../program_structure\" }\n\n[dev-dependencies]\nparser = { package = \"circomspect-parser\", version = \"2.1.3\", path = \"../parser\" }\nprogram_structure = { package = \"circomspect-program-structure\", version = \"2.1.3\", path = \"../program_structure\" }\n"
  },
  {
    "path": "program_structure_tests/src/control_flow_graph.rs",
    "content": "use std::collections::{HashMap, HashSet};\n\nuse parser::parse_definition;\nuse program_structure::cfg::*;\nuse program_structure::constants::Curve;\nuse program_structure::report::ReportCollection;\nuse program_structure::ir::VariableName;\n\n#[test]\nfn test_cfg_from_if() {\n    let src = r#\"\n        function f(x) {\n            var y = 0;\n            if (x > 0) {\n                y = x;\n                y += y * x;\n            }\n            return y + x;\n        }\n    \"#;\n    validate_cfg(\n        src,\n        &[\"x\", \"y\"],\n        &[3, 2, 1],\n        &[0, 0, 0],\n        &[(vec![], vec![1, 2]), (vec![0], vec![2]), (vec![0, 1], vec![])],\n    );\n}\n\n#[test]\nfn test_cfg_from_if_then_else() {\n    let src = r#\"\n        function f(x) {\n            var y = 0;\n            if (x > 0) {\n                y = x;\n                y += y * x;\n            } else {\n                x = y;\n                x += x + 1;\n            }\n            return y + x;\n        }\n    \"#;\n    validate_cfg(\n        src,\n        &[\"x\", \"y\"],\n        &[3, 2, 2, 1],\n        &[0, 0, 0, 0],\n        &[(vec![], vec![1, 2]), (vec![0], vec![3]), (vec![0], vec![3]), (vec![1, 2], vec![])],\n    );\n}\n\n#[test]\nfn test_cfg_from_while() {\n    let src = r#\"\n        function f(x) {\n            var y = 0;\n            while (y < x) {\n                y += y ** 2 + 1;\n            }\n            return y + x;\n        }\n    \"#;\n    validate_cfg(\n        src,\n        &[\"x\", \"y\"],\n        &[2, 1, 1, 1],\n        &[0, 0, 1, 0],\n        &[\n            (vec![], vec![1]),\n            // 0:\n            // var y;\n            // y = 0;\n            (vec![0, 2], vec![2, 3]),\n            // 1:\n            // if (y < 0)\n            (vec![1], vec![1]),\n            //   2:\n            //   y += y ** 2 + 1\n            (vec![1], vec![]),\n            // 3:\n            // return y + x;\n        ],\n    );\n}\n\n#[test]\nfn test_cfg_from_nested_if() {\n    let src = r#\"\n        function f(x) {\n            var y = 0;\n            if (y <= x) {\n                y *= 2;\n                if (y == x) {\n                    y *= 2;\n                }\n            }\n            return y + x;\n        }\n    \"#;\n    validate_cfg(\n        src,\n        &[\"x\", \"y\"],\n        &[3, 2, 1, 1],\n        &[0, 0, 0, 0],\n        &[\n            (vec![], vec![1, 3]),\n            // 0:\n            // var y;\n            // y = 0;\n            // if (y <= 0)\n            (vec![0], vec![2, 3]),\n            //   1:\n            //   y *= 2;\n            //   if (y == x)\n            (vec![1], vec![3]),\n            //     2:\n            //     y *= 2;\n            (vec![0, 1, 2], vec![]),\n            // 3:\n            // return y + x;\n        ],\n    );\n}\n\n#[test]\nfn test_cfg_from_nested_while() {\n    let src = r#\"\n        function f(x) {\n            var y = 0;\n            while (y <= x) {\n                y *= 2;\n                while (y < x) {\n                    y *= 2;\n                }\n            }\n            return y + x;\n        }\n    \"#;\n    validate_cfg(\n        src,\n        &[\"x\", \"y\"],\n        &[2, 1, 1, 1, 1, 1],\n        &[0, 0, 1, 1, 2, 0],\n        &[\n            (vec![], vec![1]),\n            // 0:\n            // var y;\n            // y = 0;\n            (vec![0, 3], vec![2, 5]),\n            // 1:\n            // if (y <= 0)\n            (vec![1], vec![3]),\n            //   2:\n            //   y *= 2;\n            (vec![2, 4], vec![4, 1]),\n            //   3:\n            //   if (y < x)\n            (vec![3], vec![3]),\n            //     4:\n            //     y *= 2;\n            (vec![1], vec![]),\n            // 5:\n            // return y + x;\n        ],\n    );\n}\n\n#[test]\nfn test_cfg_with_non_unique_variables() {\n    let src = r#\"\n        template T(n){\n            signal input in;\n            signal output out[2];\n\n            component comp[2];\n            if ((n % 2) == 0) {\n                for(var i = 0; i < 2; i++) {\n                    comp[i].in <== in;\n                }\n            } else {\n                for(var i = 0; i < 2; i++) {\n                    out[i] <== comp[i].out;\n                }\n            }\n        }\n    \"#;\n\n    validate_cfg(\n        src,\n        &[\"n\", \"in\", \"out\", \"comp\", \"i\", \"i.0\"],\n        &[4, 2, 1, 2, 2, 1, 2],\n        &[0, 0, 0, 1, 0, 0, 1],\n        &[\n            (vec![], vec![1, 4]),\n            // 0:\n            // signal input in;\n            // signal output out[2];\n            // component comp[2];\n            // if ((n % 2) == 0)\n            (vec![0], vec![2]),\n            //   1:\n            //   var i;\n            //   i = 0;\n            (vec![1, 3], vec![3]),\n            //   2:\n            //   if (i < 2)\n            (vec![2], vec![2]),\n            //     3:\n            //     comp[i].in = in;\n            //     i++;\n            (vec![0], vec![5]),\n            //   4:\n            //   var i_0;\n            //   i_0 = 0;\n            (vec![4, 6], vec![6]),\n            //   5:\n            //   if (i_0 < 2)\n            (vec![5], vec![5]),\n            //     6:\n            //     out[i] <== comp[i_0].out;\n            //     i_0++;\n        ],\n    );\n}\n\n#[test]\nfn test_dominance_from_nested_if() {\n    // 0:\n    // var y;\n    // y = 0;\n    // if (y <= 0)\n    //\n    //   1:\n    //   y *= 2;\n    //   if (y == x)\n    //\n    //     2:\n    //     y *= 2;\n    //\n    // 3:\n    // return y + x;\n    let src = r#\"\n        function f(x) {\n            var y = 0;\n            if (y <= x) {\n                y *= 2;\n                if (y == x) {\n                    y *= 2;\n                }\n            }\n            return y + x;\n        }\n    \"#;\n\n    let mut immediate_dominators = HashMap::new();\n    immediate_dominators.insert(0, None);\n    immediate_dominators.insert(1, Some(0));\n    immediate_dominators.insert(2, Some(1));\n    immediate_dominators.insert(3, Some(0));\n\n    let mut dominance_frontier = HashMap::new();\n    dominance_frontier.insert(0, HashSet::new());\n    dominance_frontier.insert(1, HashSet::from([3]));\n    dominance_frontier.insert(2, HashSet::from([3]));\n    dominance_frontier.insert(3, HashSet::new());\n\n    validate_dominance(src, &immediate_dominators, &dominance_frontier);\n}\n\n#[test]\nfn test_dominance_from_nested_if_then_else() {\n    // 0:\n    // var y;\n    // y = 2;\n    // if (x > 0)\n    //\n    //   1:\n    //   return x * y;\n    //\n    //   2:\n    //   if (x < 0)\n    //\n    //     3:\n    //     return x - y;\n    //\n    //     4:\n    //     return y;\n    let src = r#\"\n        function f(x) {\n            var y = 2;\n            if (x > 0) {\n                return x * y;\n            } else {\n                if (x < 0) {\n                    return x - y;\n                } else {\n                    return y;\n                }\n            }\n        }\n    \"#;\n\n    let mut immediate_dominators = HashMap::new();\n    immediate_dominators.insert(0, None);\n    immediate_dominators.insert(1, Some(0));\n    immediate_dominators.insert(2, Some(0));\n    immediate_dominators.insert(3, Some(2));\n    immediate_dominators.insert(4, Some(2));\n\n    let mut dominance_frontier = HashMap::new();\n    dominance_frontier.insert(0, HashSet::new());\n    dominance_frontier.insert(1, HashSet::new());\n    dominance_frontier.insert(2, HashSet::new());\n    dominance_frontier.insert(3, HashSet::new());\n\n    validate_dominance(src, &immediate_dominators, &dominance_frontier);\n}\n\n#[test]\nfn test_branches_from_nested_if_then_else() {\n    // 0:\n    // var y;\n    // y = 2;\n    // if (x > 0)\n    //\n    //   1:\n    //   return x * y;\n    //\n    //   2:\n    //   if (x < 0)\n    //\n    //     3:\n    //     return x - y;\n    //\n    //     4:\n    //     return y;\n    let src = r#\"\n        function f(x) {\n            var y = 2;\n            if (x > 0) {\n                return x * y;\n            } else {\n                if (x < 0) {\n                    return x - y;\n                } else {\n                    return y;\n                }\n            }\n        }\n    \"#;\n\n    let mut true_branches = HashMap::new();\n    true_branches.insert(0, HashSet::from([1]));\n    true_branches.insert(2, HashSet::from([3]));\n\n    let mut false_branches = HashMap::new();\n    false_branches.insert(0, HashSet::from([2, 3, 4]));\n    false_branches.insert(2, HashSet::from([4]));\n\n    validate_branches(src, &true_branches, &false_branches);\n}\n\n#[test]\nfn test_branches_from_nested_if() {\n    // 0:\n    // var y;\n    // y = 0;\n    // if (y <= 0)\n    //\n    //   1:\n    //   y *= 2;\n    //   if (y == x)\n    //\n    //     2:\n    //     y *= 2;\n    //\n    // 3:\n    // return y + x;\n    let src = r#\"\n        function f(x) {\n            var y = 0;\n            if (y <= x) {\n                y *= 2;\n                if (y == x) {\n                    y *= 2;\n                }\n            }\n            return y + x;\n        }\n    \"#;\n\n    let mut true_branches = HashMap::new();\n    true_branches.insert(0, HashSet::from([1, 2]));\n    true_branches.insert(1, HashSet::from([2]));\n\n    let mut false_branches = HashMap::new();\n    false_branches.insert(0, HashSet::new());\n    false_branches.insert(1, HashSet::new());\n\n    validate_branches(src, &true_branches, &false_branches);\n}\n\nfn validate_cfg(\n    src: &str,\n    variables: &[&str],\n    lengths: &[usize],\n    loop_depths: &[usize],\n    edges: &[(Vec<Index>, Vec<Index>)],\n) {\n    // 1. Generate CFG from source.\n    let mut reports = ReportCollection::new();\n    let cfg = parse_definition(src).unwrap().into_cfg(&Curve::default(), &mut reports).unwrap();\n    assert!(reports.is_empty());\n\n    // 2. Verify declared variables.\n    assert_eq!(\n        cfg.variables().cloned().collect::<HashSet<_>>(),\n        variables.iter().map(|name| lift(name)).collect::<HashSet<_>>()\n    );\n\n    // 3. Validate block lengths.\n    for (basic_block, length) in cfg.iter().zip(lengths.iter()) {\n        assert_eq!(basic_block.len(), *length);\n    }\n\n    // 3. Validate loop depths.\n    for (basic_block, loop_depth) in cfg.iter().zip(loop_depths.iter()) {\n        assert_eq!(basic_block.loop_depth(), *loop_depth);\n    }\n\n    // 4. Validate block edges against input.\n    for (basic_block, edges) in cfg.iter().zip(edges.iter()) {\n        let actual_predecessors = basic_block.predecessors();\n        let expected_predecessors: HashSet<_> = edges.0.iter().cloned().collect();\n        assert_eq!(\n            actual_predecessors,\n            &expected_predecessors,\n            \"unexpected predecessor set for block {}\",\n            basic_block.index()\n        );\n\n        let actual_successors = basic_block.successors();\n        let expected_successors: HashSet<_> = edges.1.iter().cloned().collect();\n        assert_eq!(\n            actual_successors,\n            &expected_successors,\n            \"unexpected successor set for block {}\",\n            basic_block.index()\n        );\n    }\n\n    // 5. Check that block j is a successor of i iff i is a predecessor of j.\n    for first_block in cfg.iter() {\n        for second_block in cfg.iter() {\n            assert_eq!(\n                first_block.successors().contains(&second_block.index()),\n                second_block.predecessors().contains(&first_block.index()),\n                \"basic block {} is not a predecessor of a successor block {}\",\n                first_block.index(),\n                second_block.index()\n            );\n        }\n    }\n}\n\nfn validate_dominance(\n    src: &str,\n    immediate_dominators: &HashMap<usize, Option<usize>>,\n    dominance_frontier: &HashMap<usize, HashSet<usize>>,\n) {\n    // 1. Generate CFG from source.\n    let mut reports = ReportCollection::new();\n    let cfg = parse_definition(src).unwrap().into_cfg(&Curve::default(), &mut reports).unwrap();\n    assert!(reports.is_empty());\n\n    // 2. Validate immediate dominators.\n    for (index, expected_dominator) in immediate_dominators {\n        let basic_block = cfg.get_basic_block(*index).unwrap();\n        let immediate_dominator =\n            cfg.get_immediate_dominator(basic_block).map(|dominator_block| dominator_block.index());\n        assert_eq!(&immediate_dominator, expected_dominator);\n    }\n\n    // 3. Validate dominance frontier.\n    for (index, expected_frontier) in dominance_frontier {\n        let basic_block = cfg.get_basic_block(*index).unwrap();\n        let dominance_frontier = cfg\n            .get_dominance_frontier(basic_block)\n            .iter()\n            .map(|frontier_block| frontier_block.index())\n            .collect::<HashSet<_>>();\n        assert_eq!(&dominance_frontier, expected_frontier);\n    }\n}\n\nfn validate_branches(\n    src: &str,\n    true_branches: &HashMap<usize, HashSet<usize>>,\n    false_branches: &HashMap<usize, HashSet<usize>>,\n) {\n    // 1. Generate CFG from source.\n    let mut reports = ReportCollection::new();\n    let cfg = parse_definition(src).unwrap().into_cfg(&Curve::default(), &mut reports).unwrap();\n    assert!(reports.is_empty());\n\n    // 2. Validate the set of true branches.\n    for (header_index, expected_indices) in true_branches {\n        let header_block = cfg.get_basic_block(*header_index).unwrap();\n        let true_branch = cfg.get_true_branch(header_block);\n        let true_indices =\n            true_branch.iter().map(|basic_block| basic_block.index()).collect::<HashSet<_>>();\n        assert_eq!(&true_indices, expected_indices);\n    }\n\n    // 3. Validate the set of false branches.\n    for (header_index, expected_indices) in false_branches {\n        let header_block = cfg.get_basic_block(*header_index).unwrap();\n        let false_branch = cfg.get_false_branch(header_block);\n        let false_indices =\n            false_branch.iter().map(|basic_block| basic_block.index()).collect::<HashSet<_>>();\n        assert_eq!(&false_indices, expected_indices);\n    }\n}\n\nfn lift(name: &str) -> VariableName {\n    // We assume that the input string uses '.' to separate the name from the suffix.\n    let tokens: Vec<_> = name.split('.').collect();\n    match tokens.len() {\n        1 => VariableName::from_string(tokens[0]),\n        2 => VariableName::from_string(tokens[0]).with_suffix(tokens[1]),\n        _ => panic!(\"invalid variable name\"),\n    }\n}\n"
  },
  {
    "path": "program_structure_tests/src/lib.rs",
    "content": "#[cfg(test)]\nmod control_flow_graph;\n\n#[cfg(test)]\nmod static_single_assignment;\n"
  },
  {
    "path": "program_structure_tests/src/static_single_assignment.rs",
    "content": "use std::collections::HashSet;\n\nuse parser::parse_definition;\nuse program_structure::cfg::{BasicBlock, Cfg, IntoCfg};\nuse program_structure::constants::Curve;\nuse program_structure::report::ReportCollection;\nuse program_structure::ir::variable_meta::VariableMeta;\nuse program_structure::ir::{AssignOp, Statement, VariableName};\nuse program_structure::ssa::traits::SSAStatement;\n\n#[test]\nfn test_ssa_with_array() {\n    let src = r#\"\n        template F(x) {\n            var y[2] = [0, 1];\n            signal in;\n            signal out;\n            component c = G(y);\n\n            y[0] += y[1] * x;\n            c.in <== in + y;\n            out <== c.out;\n        }\n    \"#;\n    validate_ssa(src, &[\"x.0\", \"y.0\", \"y.1\", \"in\", \"out\", \"c\"]);\n}\n\n#[test]\nfn test_ssa_with_components_and_signals() {\n    let src = r#\"\n        template F(x) {\n            var y = 0;\n            signal in;\n            signal out;\n            component c = G(y);\n\n            y += y * x;\n            c.in <== in + y;\n            out <== c.out;\n        }\n    \"#;\n    validate_ssa(src, &[\"x.0\", \"y.0\", \"y.1\", \"in\", \"out\", \"c\"]);\n}\n\n#[test]\nfn test_ssa_from_if() {\n    let src = r#\"\n        function f(x) {\n            var y = 0;\n            if (x > 0) {\n                y = x;\n                y += y * x;\n            }\n            return y + x;\n        }\n    \"#;\n    validate_ssa(src, &[\"x.0\", \"y.0\", \"y.1\", \"y.2\", \"y.3\"]);\n}\n\n#[test]\nfn test_ssa_from_if_then_else() {\n    let src = r#\"\n        function f(x) {\n            var y = 0;\n            if (x > 0) {\n                y = x;\n                y += y * x;\n            } else {\n                x = y;\n                x += x + 1;\n            }\n            return y + x;\n        }\n    \"#;\n    validate_ssa(src, &[\"x.0\", \"x.1\", \"x.2\", \"x.3\", \"y.0\", \"y.1\", \"y.2\", \"y.3\"]);\n}\n\n#[test]\nfn test_ssa_from_while() {\n    let src = r#\"\n        function f(x) {\n            var y = 0;\n            while (y < x) {\n                y += y ** 2 + 1;\n            }\n            return y + x;\n        }\n    \"#;\n    validate_ssa(src, &[\"x.0\", \"y.0\", \"y.1\", \"y.2\"]);\n}\n\n#[test]\nfn test_ssa_from_nested_if() {\n    let src = r#\"\n        function f(x) {\n            var y = 0;\n            if (y <= x) {\n                y *= 2;\n                if (y == x) {\n                    y *= 2;\n                }\n            }\n            return y + x;\n        }\n    \"#;\n    validate_ssa(src, &[\"x.0\", \"y.0\", \"y.1\", \"y.2\", \"y.3\"]);\n}\n\n#[test]\nfn test_ssa_from_nested_while() {\n    let src = r#\"\n        function f(x) {\n            var y = 0;\n            while (y <= x) {\n                y *= 2;\n                while (y < x) {\n                    y *= 2;\n                }\n            }\n            return y + x;\n        }\n    \"#;\n    validate_ssa(src, &[\"x.0\", \"y.0\", \"y.1\", \"y.2\", \"y.3\", \"y.4\"]);\n}\n\n#[test]\nfn test_ssa_with_non_unique_variables() {\n    let src = r#\"\n        template T(n){\n            signal input in;\n            signal output out[2];\n\n            component comp[2];\n            if ((n % 2) == 0) {\n                for(var i = 0; i < 2; i++) {\n                    comp[i].in <== in;\n                }\n            } else {\n                for(var i = 0; i < 2; i++) {\n                    out[i] <== comp[i].out;\n                }\n            }\n        }\n    \"#;\n\n    validate_ssa(\n        src,\n        &[\"n.0\", \"in\", \"out\", \"comp\", \"i.0\", \"i.1\", \"i.2\", \"i_0.0\", \"i_0.1\", \"i_0.2\"],\n    );\n}\nfn validate_ssa(src: &str, variables: &[&str]) {\n    // 1. Generate CFG and convert to SSA.\n    let mut reports = ReportCollection::new();\n    let cfg = parse_definition(src)\n        .unwrap()\n        .into_cfg(&Curve::default(), &mut reports)\n        .unwrap()\n        .into_ssa()\n        .unwrap();\n    assert!(reports.is_empty());\n\n    // 2. Check that each variable is assigned at most once.\n    use AssignOp::*;\n    use Statement::*;\n    let mut assignments = HashSet::new();\n    let result = cfg\n        .iter()\n        .flat_map(|basic_block| basic_block.iter())\n        .filter_map(|stmt| match stmt {\n            Substitution { var, op: AssignLocalOrComponent, .. } => Some(var),\n            _ => None,\n        })\n        .all(|name| assignments.insert(name));\n    assert!(result);\n\n    // 3. Check that all variables are written before they are read.\n    let mut env = cfg.parameters().iter().cloned().collect();\n    validate_reads(cfg.entry_block(), &cfg, &mut env);\n\n    // 4. Verify declared variables.\n    assert_eq!(\n        cfg.variables()\n            .map(|name| format!(\"{:?}\", name)) // Must use debug formatting here to include suffix and version.\n            .collect::<HashSet<_>>(),\n        variables.iter().map(|name| name.to_string()).collect::<HashSet<_>>()\n    );\n}\n\nfn validate_reads(current_block: &BasicBlock, cfg: &Cfg, env: &mut HashSet<VariableName>) {\n    for stmt in current_block.iter() {\n        // Ignore phi function arguments as they may be generated from a loop back-edge.\n        if !stmt.is_phi_statement() {\n            // Check that all read variables are in the environment.\n            for var_use in stmt.locals_read() {\n                assert!(\n                    env.contains(var_use.name()),\n                    \"variable `{}` is read before it is written\",\n                    var_use.name(),\n                );\n            }\n        }\n        // Check that no written variables are in the environment.\n        for var_use in VariableMeta::locals_written(stmt) {\n            assert!(\n                env.insert(var_use.name().clone()),\n                \"variable `{}` is written multiple times\",\n                var_use.name(),\n            );\n        }\n    }\n    // Recurse into successors.\n    for successor_block in cfg.get_dominator_successors(current_block) {\n        validate_reads(successor_block, cfg, &mut env.clone());\n    }\n}\n"
  }
]