diff --git a/ref/harec/COPYING b/ref/harec/COPYING
new file mode 100644
index 00000000..f288702d
--- /dev/null
+++ b/ref/harec/COPYING
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/ref/harec/include/arch.h b/ref/harec/include/arch.h
new file mode 100644
index 00000000..9dce452d
--- /dev/null
+++ b/ref/harec/include/arch.h
@@ -0,0 +1,11 @@
+#ifndef HAREC_ARCH_H
+#define HAREC_ARCH_H
+
+enum arch {
+ AARCH64,
+ PPC64LE,
+ RISCV64,
+ X86_64,
+};
+
+#endif
diff --git a/ref/harec/include/ast.h b/ref/harec/include/ast.h
new file mode 100644
index 00000000..6380fe8d
--- /dev/null
+++ b/ref/harec/include/ast.h
@@ -0,0 +1,444 @@
+#ifndef HARE_AST_H
+#define HARE_AST_H
+#include
+#include
+#include "expr.h"
+#include "identifier.h"
+#include "lex.h"
+#include "types.h"
+
+struct ast_type;
+
+enum ast_import_mode {
+ IMPORT_NORMAL, // use foo::bar;
+ IMPORT_ALIAS, // use foo = bar::baz;
+ IMPORT_MEMBERS, // use foo::{bar, baz};
+ IMPORT_WILDCARD, // use foo::bar::*;
+};
+
+struct ast_import_members {
+ struct location loc;
+ struct ident *name;
+ struct ast_import_members *next;
+};
+
+struct ast_imports {
+ enum ast_import_mode mode;
+ struct ident *ident;
+ union {
+ const char *alias;
+ struct ast_import_members *members;
+ };
+ struct ast_imports *next;
+};
+
+struct ast_array_type {
+ struct ast_expression *length; // NULL for unbounded arrays
+ struct ast_type *members;
+ bool contextual;
+};
+
+struct ast_slice_type {
+ struct ast_type *members;
+};
+
+struct ast_enum_field {
+ struct location loc;
+ struct ident *name;
+ struct ast_expression *value;
+ struct ast_enum_field *next;
+};
+
+struct ast_enum_type {
+ enum type_storage storage;
+ struct ast_enum_field *values;
+};
+
+struct ast_function_parameters {
+ struct location loc;
+ struct ident *name;
+ struct ast_type *type;
+ struct ast_expression *default_value;
+ struct ast_function_parameters *next;
+};
+
+struct ast_function_type {
+ struct ast_type *result;
+ struct ast_function_parameters *params;
+ enum variadism variadism;
+};
+
+struct ast_pointer_type {
+ struct ast_type *referent;
+ bool nullable;
+};
+
+struct ast_tagged_union_type {
+ struct ast_type *type;
+ bool unwrap;
+ struct ast_tagged_union_type *next;
+};
+
+struct ast_tuple_type {
+ struct ast_type *type;
+ struct ast_tuple_type *next;
+};
+
+struct ast_struct_union_field {
+ struct ast_struct_union_field *next;
+ const char *name; // null if embed, may be "_"
+ struct ast_type *type;
+};
+
+struct ast_struct_union_type {
+ struct ast_struct_union_field fields;
+ bool packed;
+};
+
+struct ast_type {
+ struct location loc;
+ enum type_storage storage;
+ union {
+ struct ast_array_type array;
+ struct ast_type *error;
+ struct ast_function_type func;
+ struct ast_pointer_type pointer;
+ struct ast_slice_type slice;
+ struct ast_struct_union_type struct_union;
+ struct ast_tagged_union_type tagged;
+ struct ast_tuple_type tuple;
+ struct {
+ struct ident *alias;
+ // Only valid for enums
+ struct ast_enum_type _enum;
+ };
+ };
+};
+
+struct ast_types {
+ const struct ast_type *type;
+ struct ast_types *next;
+};
+
+struct ast_expression_list {
+ struct ast_expression *expr;
+ struct ast_expression_list *next;
+};
+
+struct ast_expression_access {
+ enum access_type type;
+ union {
+ struct ident *ident;
+ struct {
+ struct ast_expression *array;
+ struct ast_expression *index;
+ };
+ struct {
+ struct ast_expression *_struct;
+ const char *field;
+ };
+ struct {
+ struct ast_expression *tuple;
+ struct ast_expression *value;
+ };
+ };
+};
+
+struct ast_expression_alloc {
+ enum alloc_kind kind;
+ struct ast_expression *init;
+ struct ast_expression *cap;
+};
+
+struct ast_expression_append {
+ struct ast_expression *object;
+ struct ast_expression *value;
+ struct ast_expression *length;
+ bool is_static, is_multi;
+};
+
+struct ast_expression_assert {
+ struct ast_expression *cond;
+ struct ast_expression *message;
+ bool is_static;
+};
+
+struct ast_expression_assign {
+ enum binarithm_operator op;
+ // object == NULL for discarding assignment (`_ = foo`)
+ struct ast_expression *object, *value;
+};
+
+struct ast_expression_binarithm {
+ enum binarithm_operator op;
+ struct ast_expression *lvalue, *rvalue;
+};
+
+struct ast_binding_names {
+ struct ident *name; // NULL for _
+ struct ast_binding_names *next;
+};
+
+struct ast_expression_binding {
+ // more than one name means tuple unpacking,
+ // otherwise it's a regular binding
+ struct ast_binding_names names;
+ struct ast_type *type;
+ bool is_static;
+ struct ast_expression *initializer;
+ struct ast_expression_binding *next;
+};
+
+struct ast_expression_call {
+ struct ast_expression *lvalue;
+ struct ast_expression_list *args;
+ bool variadic; // last argument is a variadic argument list
+};
+
+struct ast_expression_cast {
+ enum cast_kind kind;
+ struct ast_expression *value;
+ struct ast_type *type;
+};
+
+struct ast_expression_literal {
+ enum type_storage storage;
+ union {
+ int64_t ival;
+ uint64_t uval;
+ double fval;
+ uint32_t rune;
+ bool bval;
+ struct {
+ size_t len;
+ char *value;
+ } string;
+ struct {
+ struct ast_expression_list *exprs;
+ bool expand;
+ } array;
+ };
+};
+
+struct ast_expression_control {
+ const char *label; // Never set for return.
+ struct ast_expression *value; // Never set for continue
+};
+
+struct ast_expression_defer {
+ struct ast_expression *deferred;
+};
+
+struct ast_expression_delete {
+ struct ast_expression *expr;
+ bool is_static;
+};
+
+struct ast_expression_for {
+ enum for_kind kind;
+ const char *label;
+ struct ast_expression *bindings;
+ struct ast_expression *cond;
+ struct ast_expression *afterthought;
+ struct ast_expression *body;
+ struct ast_expression *else_branch;
+};
+
+struct ast_expression_free {
+ struct ast_expression *expr;
+};
+
+struct ast_expression_if {
+ struct ast_expression *cond;
+ struct ast_expression *true_branch, *false_branch;
+};
+
+struct ast_expression_compound {
+ const char *label;
+ struct location label_loc;
+ struct ast_expression_list list;
+};
+
+struct ast_match_case {
+ struct ident *name; // May be null
+ struct ast_type *type;
+ struct ast_expression_list exprs;
+ struct ast_match_case *next;
+};
+
+struct ast_expression_match {
+ const char *label;
+ struct ast_expression *value;
+ struct ast_match_case *cases;
+};
+
+enum measure_operator {
+ M_ALIGN,
+ M_LEN,
+ M_SIZE,
+ M_OFFSET,
+};
+
+struct ast_expression_measure {
+ enum measure_operator op;
+ union {
+ struct ast_expression *value;
+ struct ast_type *type;
+ // TODO: Field selection
+ };
+};
+
+struct ast_expression_propagate {
+ struct ast_expression *value;
+ bool abort;
+};
+
+struct ast_expression_slice {
+ struct ast_expression *object;
+ struct ast_expression *start, *end;
+};
+
+struct ast_case_option {
+ struct ast_expression *value;
+ struct ast_case_option *next;
+};
+
+struct ast_switch_case {
+ struct ast_case_option *options; // NULL for *
+ struct ast_expression_list exprs;
+ struct ast_switch_case *next;
+};
+
+struct ast_expression_switch {
+ const char *label;
+ struct ast_expression *value;
+ struct ast_switch_case *cases;
+};
+
+struct ast_field_value {
+ const char *name;
+ struct ast_type *type;
+ struct ast_expression *initializer;
+ struct ast_field_value *next;
+};
+
+struct ast_expression_struct {
+ bool autofill;
+ bool undefined;
+ struct ident *type;
+ struct ast_field_value *fields;
+};
+
+struct ast_expression_tuple {
+ struct ast_expression *expr;
+ struct ast_expression_tuple *next;
+};
+
+struct ast_expression_unarithm {
+ enum unarithm_operator op;
+ struct ast_expression *operand;
+};
+
+struct ast_expression_vaarg {
+ struct ast_expression *ap;
+ struct ast_type *type;
+};
+
+struct ast_expression {
+ struct location loc;
+ enum expr_type type;
+ union {
+ struct ast_expression_access access;
+ struct ast_expression_alloc alloc;
+ struct ast_expression_append append; // also insert
+ struct ast_expression_assert assert;
+ struct ast_expression_assign assign;
+ struct ast_expression_binarithm binarithm;
+ struct ast_expression_binding binding;
+ struct ast_expression_call call;
+ struct ast_expression_cast cast;
+ struct ast_expression_compound compound;
+ struct ast_expression_control control;
+ struct ast_expression_defer defer;
+ struct ast_expression_delete delete;
+ struct ast_expression_for _for;
+ struct ast_expression_free free;
+ struct ast_expression_if _if;
+ struct ast_expression_literal literal;
+ struct ast_expression_match match;
+ struct ast_expression_measure measure;
+ struct ast_expression_propagate propagate;
+ struct ast_expression_slice slice;
+ struct ast_expression_struct _struct;
+ struct ast_expression_switch _switch;
+ struct ast_expression_tuple tuple;
+ struct ast_expression_unarithm unarithm;
+ struct ast_expression_vaarg vaarg;
+ };
+};
+
+struct ast_global_decl {
+ const char *symbol;
+ bool threadlocal;
+ struct ident *ident;
+ struct ast_type *type;
+ struct ast_expression *init;
+};
+
+struct ast_type_decl {
+ struct ident *ident;
+ struct ast_type *type;
+};
+
+enum func_decl_flags {
+ FN_FINI = 1 << 0,
+ FN_INIT = 1 << 1,
+ FN_TEST = 1 << 2,
+};
+
+struct ast_function_decl {
+ const char *symbol;
+ struct ident *ident;
+ struct ast_function_type prototype;
+ struct ast_expression *body;
+ enum func_decl_flags flags;
+};
+
+enum ast_decl_type {
+ ADECL_FUNC,
+ ADECL_TYPE,
+ ADECL_GLOBAL,
+ ADECL_CONST,
+ ADECL_ASSERT,
+};
+
+struct ast_decl {
+ struct location loc;
+ enum ast_decl_type decl_type;
+ bool exported;
+ union {
+ struct ast_global_decl global;
+ struct ast_global_decl constant;
+ struct ast_type_decl type;
+ struct ast_function_decl function;
+ struct ast_expression_assert assert;
+ };
+};
+
+struct ast_decls {
+ struct ast_decl decl;
+ struct ast_decls *next;
+};
+
+struct ast_subunit {
+ struct ast_imports *imports;
+ struct ast_decls *decls;
+ struct ast_subunit *next;
+};
+
+struct ast_unit {
+ struct ident *ns;
+ struct ast_subunit subunits;
+};
+
+#endif
diff --git a/ref/harec/include/check.h b/ref/harec/include/check.h
new file mode 100644
index 00000000..3e8a6035
--- /dev/null
+++ b/ref/harec/include/check.h
@@ -0,0 +1,169 @@
+#ifndef HARE_CHECK_H
+#define HARE_CHECK_H
+#include
+#include
+#include "ast.h"
+#include "identifier.h"
+#include "scope.h"
+#include "types.h"
+#include "type_store.h"
+#include "util.h"
+
+struct expression;
+
+#define MODCACHE_BUCKETS 256
+
+struct modcache {
+ struct ident *ident;
+ struct scope *scope;
+ struct modcache *next;
+};
+
+struct errors {
+ struct location loc;
+ char *msg;
+ struct errors *next;
+};
+
+struct context {
+ type_store *store;
+ struct modcache **modcache;
+ const struct type *fntype;
+ struct ident *ns;
+ struct scope *unit;
+ struct scope *scope;
+ struct scope *defines;
+ const char *mainsym;
+ struct ident *mainident;
+ bool is_test;
+ int id;
+ struct errors *errors;
+ struct errors **next;
+ struct declarations *decls;
+ struct ast_types *unresolved;
+ struct intern_table *itbl;
+};
+
+struct constant_decl {
+ const struct type *type;
+ const struct expression *value;
+};
+
+struct function_decl {
+ const struct type *type;
+ struct expression *body;
+ struct scope *scope;
+ unsigned int flags; // enum func_decl_flags
+};
+
+struct global_decl {
+ const struct type *type;
+ struct expression *value; // EXPR_LITERAL
+ bool threadlocal;
+};
+
+enum decl_type {
+ DECL_FUNC,
+ DECL_TYPE,
+ DECL_GLOBAL,
+ DECL_CONST,
+};
+
+struct declaration {
+ enum decl_type decl_type;
+ int file;
+ struct ident *ident;
+ const char *symbol;
+ bool exported; // XXX: this bool takes up 8 bytes and i am in pain
+ union {
+ struct constant_decl constant;
+ struct function_decl func;
+ struct global_decl global;
+ const struct type *type;
+ };
+};
+
+struct declarations {
+ struct declaration decl;
+ struct declarations *next;
+};
+
+struct unit {
+ struct ident *ns;
+ struct declarations *declarations;
+ struct identifiers *imports;
+};
+
+enum idecl_type {
+ IDECL_DECL,
+ IDECL_ENUM_FLD,
+};
+
+// Keeps track of enum specific context required for enum field resolution
+struct incomplete_enum_field {
+ struct ast_enum_field *field;
+ struct scope *enum_scope;
+};
+
+// Keeps track of context required to resolve a declaration or an enum field
+// Extends the scope_object struct so it can be inserted into a scope
+struct incomplete_decl {
+ struct scope *imports; // the scope of this declaration's subunit
+ enum idecl_type type;
+ bool in_progress;
+ bool dealias_in_progress;
+ union {
+ struct ast_decl decl;
+ struct incomplete_enum_field *field;
+ };
+};
+
+struct ident *mkident(struct context *ctx, struct ident *ident,
+ const char *symbol);
+
+void append_decl(struct context *ctx, struct declaration *decl);
+
+void mkstrliteral(struct expression *expr, const char *fmt, ...) FORMAT(2, 3);
+
+char *gen_typename(const struct type *type);
+
+struct expression *lower_implicit_cast(struct context *ctx,
+ const struct type *to, struct expression *expr);
+
+typedef void (*resolvefn)(struct context *, struct scope_object *obj);
+
+void resolve_dimensions(struct context *ctx, struct scope_object *obj);
+
+void resolve_type(struct context *ctx, struct scope_object *obj);
+
+void wrap_resolver(struct context *ctx,
+ struct scope_object *obj, resolvefn resolver);
+
+struct scope *check(type_store *ts,
+ bool is_test,
+ const char *mainsym,
+ struct ident *mainident,
+ const struct ast_decls *defines,
+ const struct ast_unit *aunit,
+ struct unit *unit,
+ struct intern_table *itbl);
+
+struct scope *check_internal(type_store *ts,
+ struct modcache **cache,
+ bool is_test,
+ const char *mainsym,
+ struct ident *mainident,
+ const struct ast_decls *defines,
+ const struct ast_unit *aunit,
+ struct unit *unit,
+ struct intern_table *itbl,
+ bool scan_only);
+
+void check_expression(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint);
+
+void error(struct context *ctx, struct location loc,
+ struct expression *expr, const char *fmt, ...) FORMAT(4, 5);
+#endif
diff --git a/ref/harec/include/eval.h b/ref/harec/include/eval.h
new file mode 100644
index 00000000..add25fd2
--- /dev/null
+++ b/ref/harec/include/eval.h
@@ -0,0 +1,12 @@
+#ifndef HAREC_EVAL_H
+#define HAREC_EVAL_H
+#include
+
+struct expression;
+struct context;
+
+// Evaluates an expression at compile time.
+bool eval_expr(struct context *ctx, const struct expression *restrict in,
+ struct expression *restrict out);
+
+#endif
diff --git a/ref/harec/include/expr.h b/ref/harec/include/expr.h
new file mode 100644
index 00000000..21883fa3
--- /dev/null
+++ b/ref/harec/include/expr.h
@@ -0,0 +1,402 @@
+#ifndef HAREC_EXPR_H
+#define HAREC_EXPR_H
+#include
+#include "lex.h"
+#include "types.h"
+
+struct scope;
+struct scope_object;
+
+enum expr_type {
+ EXPR_ACCESS,
+ EXPR_ALLOC,
+ EXPR_APPEND,
+ EXPR_ASSERT,
+ EXPR_ASSIGN,
+ EXPR_BINARITHM,
+ EXPR_BINDING,
+ EXPR_BREAK,
+ EXPR_CALL,
+ EXPR_CAST,
+ EXPR_COMPOUND,
+ EXPR_CONTINUE,
+ EXPR_DEFER,
+ EXPR_DEFINE,
+ EXPR_DELETE,
+ EXPR_FOR,
+ EXPR_FREE,
+ EXPR_IF,
+ EXPR_INSERT,
+ EXPR_LEN,
+ EXPR_MEASURE = EXPR_LEN, // for use in AST
+ EXPR_LITERAL,
+ EXPR_MATCH,
+ EXPR_PROPAGATE,
+ EXPR_RETURN,
+ EXPR_SLICE,
+ EXPR_STRUCT,
+ EXPR_SWITCH,
+ EXPR_TUPLE,
+ EXPR_UNARITHM,
+ EXPR_UNDEFINED,
+ EXPR_VAARG,
+ EXPR_VAEND,
+ EXPR_VASTART,
+ EXPR_YIELD,
+};
+
+struct expressions {
+ struct expression *expr;
+ struct expressions *next;
+};
+
+enum access_type {
+ ACCESS_IDENTIFIER,
+ ACCESS_INDEX,
+ ACCESS_FIELD,
+ ACCESS_TUPLE,
+};
+
+struct expression_access {
+ enum access_type type;
+ union {
+ struct scope_object *object;
+ struct {
+ struct expression *array;
+ struct expression *index;
+ bool bounds_checked;
+ };
+ struct {
+ struct expression *_struct;
+ const struct struct_field *field;
+ };
+ struct {
+ struct expression *tuple;
+ const struct type_tuple *tvalue;
+ size_t tindex;
+ };
+ };
+};
+
+enum alloc_kind {
+ ALLOC_OBJECT, // alloc(42)
+ ALLOC_CAP, // alloc([], 42)
+ ALLOC_LEN, // alloc([0...], 42)
+ ALLOC_COPY, // alloc(x...);
+};
+
+struct expression_alloc {
+ enum alloc_kind kind;
+ const struct type *allocation_result;
+ struct expression *init;
+ struct expression *cap;
+};
+
+struct expression_append {
+ struct expression *object;
+ struct expression *value;
+ struct expression *length;
+ bool is_static, is_multi;
+};
+
+enum fixed_aborts {
+ ABORT_OOB = 0,
+ ABORT_TYPE_ASSERTION = 1,
+ ABORT_UNREACHABLE = 2,
+ ABORT_CAP_TOO_SMALL = 3,
+ ABORT_ANON_ASSERTION_FAILED = 4,
+ ABORT_PROPAGATE_ERROR_OCCURRED = 5,
+};
+
+struct expression_assert {
+ struct expression *cond;
+ struct expression *message;
+ enum fixed_aborts fixed_reason;
+};
+
+enum binarithm_operator {
+ BIN_BAND, // &
+ BIN_BOR, // |
+ BIN_DIV, // /
+ BIN_GREATER, // >
+ BIN_GREATEREQ, // >=
+ BIN_LAND, // &&
+ BIN_LEQUAL, // ==
+ BIN_LESS, // <
+ BIN_LESSEQ, // <=
+ BIN_LOR, // ||
+ BIN_LSHIFT, // <<
+ BIN_LXOR, // ^^
+ BIN_MINUS, // -
+ BIN_MODULO, // %
+ BIN_NEQUAL, // !=
+ BIN_PLUS, // +
+ BIN_RSHIFT, // >>
+ BIN_TIMES, // *
+ BIN_BXOR, // ^
+ BIN_LAST = BIN_BXOR,
+};
+
+struct expression_assign {
+ enum binarithm_operator op;
+ struct expression *object, *value;
+};
+
+struct expression_binarithm {
+ enum binarithm_operator op;
+ struct expression *lvalue, *rvalue;
+};
+
+struct binding_unpack {
+ const struct scope_object *object;
+ size_t offset;
+ struct binding_unpack *next;
+};
+
+struct expression_binding {
+ const struct scope_object *object;
+ struct binding_unpack *unpack;
+ struct expression *initializer;
+ struct expression_binding *next;
+};
+
+enum cast_kind {
+ C_CAST,
+ C_ASSERTION,
+ C_TEST,
+};
+
+struct expression_cast {
+ enum cast_kind kind;
+ const struct type *secondary;
+ struct expression *value;
+ bool lowered;
+};
+
+struct call_argument {
+ struct expression *value;
+ struct call_argument *next;
+};
+
+struct expression_call {
+ struct expression *lvalue;
+ struct call_argument *args;
+};
+
+struct expression_compound {
+ const char *label;
+ struct scope *scope;
+ struct expressions exprs;
+};
+
+struct array_literal {
+ struct expression *value;
+ struct array_literal *next;
+};
+
+// Invariant: these are sorted by field offset
+struct struct_literal {
+ const struct struct_field *field;
+ struct expression *value;
+ struct struct_literal *next;
+};
+
+struct tuple_literal {
+ const struct type_tuple *field;
+ struct expression *value;
+ struct tuple_literal *next;
+};
+
+struct tagged_literal {
+ const struct type *tag;
+ struct expression *value;
+};
+
+struct expression_literal {
+ // If non-null, ival is an offset from this object's address
+ const struct scope_object *object;
+ union {
+ bool bval;
+ double fval;
+ int64_t ival;
+ uint64_t uval;
+ uint32_t rune;
+ struct {
+ size_t len;
+ char *value;
+ } string;
+ struct {
+ union {
+ // if object is null
+ struct array_literal *array;
+ // if object is non-null
+ size_t offset;
+ };
+ size_t start;
+ size_t len;
+ size_t cap;
+ } slice;
+ struct array_literal *array;
+ struct struct_literal *_struct;
+ struct tuple_literal *tuple;
+ struct tagged_literal tagged;
+ };
+};
+
+struct expression_control {
+ const char *label;
+ const struct scope *scope;
+ struct expression *value; // Only set for yield and break
+};
+
+struct expression_defer {
+ struct scope *scope;
+ struct expression *deferred;
+};
+
+struct expression_delete {
+ struct expression *expr;
+ bool is_static;
+};
+
+enum for_kind {
+ FOR_ACCUMULATOR,
+ FOR_EACH_VALUE,
+ FOR_EACH_POINTER,
+ FOR_EACH_ITERATOR,
+};
+
+struct expression_for {
+ enum for_kind kind;
+ const char *label;
+ struct scope *scope;
+ struct expression *bindings;
+ struct expression *cond;
+ struct expression *afterthought;
+ struct expression *body;
+ struct expression *else_branch;
+};
+
+struct expression_free {
+ struct expression *expr;
+};
+
+struct expression_if {
+ struct expression *cond;
+ struct expression *true_branch, *false_branch;
+};
+
+struct match_case {
+ const struct scope_object *object; // NULL if not bound
+ const struct type *type; // NULL if default
+ struct expression *value;
+ struct match_case *next;
+};
+
+struct expression_len {
+ struct expression *value;
+};
+
+struct expression_match {
+ struct expression *value;
+ struct match_case *cases;
+};
+
+struct expression_return {
+ struct expression *value;
+};
+
+struct expression_slice {
+ struct expression *object;
+ struct expression *start, *end;
+ bool bounds_checked;
+};
+
+struct case_option {
+ struct expression *value;
+ struct case_option *next;
+};
+
+struct switch_case {
+ struct case_option *options; // NULL for default case
+ struct expression *value;
+ struct switch_case *next;
+};
+
+struct expression_switch {
+ struct expression *value;
+ struct switch_case *cases;
+};
+
+struct expr_struct_field {
+ const struct struct_field *field;
+ struct expression *value;
+ struct expr_struct_field *next;
+};
+
+struct expression_struct {
+ struct expr_struct_field *fields;
+ bool autofill, undefined;
+};
+
+struct expression_tuple {
+ struct expression *value;
+ struct expression_tuple *next;
+};
+
+enum unarithm_operator {
+ UN_ADDRESS, // &
+ UN_BNOT, // ~
+ UN_DEREF, // *
+ UN_LNOT, // !
+ UN_MINUS, // -
+};
+
+struct expression_unarithm {
+ enum unarithm_operator op;
+ struct expression *operand;
+};
+
+struct expression_vaarg {
+ struct expression *ap;
+};
+
+struct expression {
+ const struct type *result;
+ enum expr_type type;
+ struct location loc; // For fixed aborts
+ union {
+ struct expression_access access;
+ struct expression_alloc alloc;
+ struct expression_append append; // and insert
+ struct expression_assert assert;
+ struct expression_assign assign;
+ struct expression_binarithm binarithm;
+ struct expression_binding binding;
+ struct expression_call call;
+ struct expression_cast cast;
+ struct expression_compound compound;
+ struct expression_defer defer;
+ struct expression_delete delete;
+ struct expression_control control;
+ struct expression_for _for;
+ struct expression_free free;
+ struct expression_if _if;
+ struct expression_len len;
+ struct expression_literal literal;
+ struct expression_match match;
+ struct expression_return _return;
+ struct expression_switch _switch;
+ struct expression_struct _struct;
+ struct expression_slice slice;
+ struct expression_tuple tuple;
+ struct expression_unarithm unarithm;
+ struct expression_vaarg vaarg;
+ void *user;
+ };
+};
+
+uint32_t expr_hash(const struct expression *expr);
+bool expr_equal(const struct expression *a, const struct expression *b);
+
+#endif
diff --git a/ref/harec/include/identifier.h b/ref/harec/include/identifier.h
new file mode 100644
index 00000000..a5614898
--- /dev/null
+++ b/ref/harec/include/identifier.h
@@ -0,0 +1,53 @@
+#ifndef HARE_IDENTIFIER_H
+#define HARE_IDENTIFIER_H
+#include
+#include
+#include
+
+// Maximum length of an identifier, as the sum of the lengths (excluding NUL
+// terminators) of its parts plus one for each namespace deliniation.
+//
+// In other words, the length of "a::b::c" is 5.
+#define IDENT_MAX 255
+
+// Minimum buffer size needed to store an unparsed identifier, including the
+// terminating NUL byte.
+#define IDENT_BUFSIZ (IDENT_MAX / 2 + IDENT_MAX + 1)
+
+struct ident {
+ const char *name;
+ struct ident *ns;
+};
+
+struct identifiers {
+ struct ident *ident;
+ struct identifiers *next;
+};
+
+struct bucket {
+ void **ids;
+ size_t sz;
+ size_t cap;
+};
+
+struct intern_table {
+ struct bucket *sbuckets;
+ struct bucket *ibuckets;
+};
+
+bool ident_equal(const struct ident *a, const struct ident *b);
+uint32_t ident_hash(uint32_t init, const struct ident *ident);
+char *ident_unparse(const struct ident *ident);
+int ident_unparse_static(const struct ident *ident, char *buf);
+const char *ident_to_sym(struct intern_table *itbl, const struct ident *ident);
+
+void intern_init(struct intern_table *itbl);
+
+const char *intern_copy(struct intern_table *itbl, const char *s);
+const char *intern_owned(struct intern_table *itbl, char *s);
+
+struct ident *intern_ident(struct intern_table *itbl,
+ const char *name, struct ident *ns);
+struct ident *intern_name(struct intern_table *itbl, const char *name);
+
+#endif
diff --git a/ref/harec/include/lex.h b/ref/harec/include/lex.h
new file mode 100644
index 00000000..7c76e58a
--- /dev/null
+++ b/ref/harec/include/lex.h
@@ -0,0 +1,190 @@
+#ifndef HAREC_LEX_H
+#define HAREC_LEX_H
+#include
+#include
+#include "types.h"
+#include "utf8.h"
+
+#define C_EOF UTF8_INVALID
+
+// Keep sorted
+enum lexical_token {
+ T_ATTR_FINI,
+ T_ATTR_INIT,
+ T_ATTR_PACKED,
+ T_ATTR_SYMBOL,
+ T_ATTR_TEST,
+ T_ATTR_THREADLOCAL,
+ T_ATTR_UNDEFINED,
+ T_UNDERSCORE,
+ T_ABORT,
+ T_ALIGN,
+ T_ALLOC,
+ T_APPEND,
+ T_AS,
+ T_ASSERT,
+ T_BOOL,
+ T_BREAK,
+ T_CASE,
+ T_CONST,
+ T_CONTINUE,
+ T_DEF,
+ T_DEFER,
+ T_DELETE,
+ T_DONE,
+ T_ELSE,
+ T_ENUM,
+ T_EXPORT,
+ T_F32,
+ T_F64,
+ T_FALSE,
+ T_FN,
+ T_FOR,
+ T_FREE,
+ T_I16,
+ T_I32,
+ T_I64,
+ T_I8,
+ T_IF,
+ T_INSERT,
+ T_INT,
+ T_IS,
+ T_LEN,
+ T_LET,
+ T_MATCH,
+ T_NEVER,
+ T_NOMEM,
+ T_NULL,
+ T_NULLABLE,
+ T_OFFSET,
+ T_OPAQUE,
+ T_RETURN,
+ T_RUNE,
+ T_SIZE,
+ T_STATIC,
+ T_STR,
+ T_STRUCT,
+ T_SWITCH,
+ T_TRUE,
+ T_TYPE,
+ T_U16,
+ T_U32,
+ T_U64,
+ T_U8,
+ T_UINT,
+ T_UINTPTR,
+ T_UNION,
+ T_USE,
+ T_VAARG,
+ T_VAEND,
+ T_VALIST,
+ T_VASTART,
+ T_VOID,
+ T_YIELD,
+ T_LAST_KEYWORD = T_YIELD,
+
+ // Operators
+ T_ARROW,
+ T_BANDEQ,
+ T_BAND,
+ T_BNOT,
+ T_BOR,
+ T_COLON,
+ T_COMMA,
+ T_DIV,
+ T_DIVEQ,
+ T_DOT,
+ T_DOUBLE_COLON,
+ T_DOUBLE_DOT,
+ T_ELLIPSIS,
+ T_EQUAL,
+ T_GREATER,
+ T_GREATEREQ,
+ T_LAND,
+ T_LANDEQ,
+ T_LBRACE,
+ T_LBRACKET,
+ T_LEQUAL,
+ T_LESS,
+ T_LESSEQ,
+ T_LNOT,
+ T_LOR,
+ T_LOREQ,
+ T_LPAREN,
+ T_LSHIFT,
+ T_LSHIFTEQ,
+ T_LXOR,
+ T_LXOREQ,
+ T_MINUS,
+ T_MINUSEQ,
+ T_MODEQ,
+ T_MODULO,
+ T_NEQUAL,
+ T_BOREQ,
+ T_PLUS,
+ T_PLUSEQ,
+ T_QUESTION,
+ T_RBRACE,
+ T_RBRACKET,
+ T_RPAREN,
+ T_RSHIFT,
+ T_RSHIFTEQ,
+ T_SEMICOLON,
+ T_TIMES,
+ T_TIMESEQ,
+ T_BXOR,
+ T_BXOREQ,
+ T_LAST_OPERATOR = T_BXOREQ,
+
+ // Tokens with additional information
+ T_NAME,
+ T_LITERAL,
+
+ // Magic tokens
+ T_EOF,
+ T_NONE,
+};
+
+struct location {
+ int file;
+ int lineno, colno;
+};
+
+struct token {
+ struct location loc;
+ enum lexical_token token;
+ enum type_storage storage;
+ union {
+ const char *name;
+ uint32_t rune;
+ int64_t ival;
+ uint64_t uval;
+ double fval;
+ struct {
+ size_t len;
+ const char *value;
+ } string;
+ };
+};
+
+struct lexer {
+ FILE *in;
+ char *buf;
+ size_t bufsz, buflen;
+ uint32_t c[2];
+ struct token un;
+ struct location loc;
+ bool require_int;
+ bool in_annotation;
+ struct intern_table *itbl;
+};
+
+void lex_init(struct lexer *lexer, FILE *f, int fileid, struct intern_table *itbl);
+void lex_finish(struct lexer *lexer);
+enum lexical_token lex(struct lexer *lexer, struct token *out);
+void unlex(struct lexer *lexer, const struct token *in);
+
+const char *token_str(const struct token *tok);
+const char *lexical_token_str(enum lexical_token tok);
+
+#endif
diff --git a/ref/harec/include/mod.h b/ref/harec/include/mod.h
new file mode 100644
index 00000000..0665354b
--- /dev/null
+++ b/ref/harec/include/mod.h
@@ -0,0 +1,11 @@
+#ifndef HARE_MOD_H
+#define HARE_MOD_H
+
+struct ast_decls;
+struct context;
+struct ident;
+
+struct scope *module_resolve(struct context *ctx,
+ const struct ast_decls *defines, struct ident *ident);
+
+#endif
diff --git a/ref/harec/include/scope.h b/ref/harec/include/scope.h
new file mode 100644
index 00000000..8a675d53
--- /dev/null
+++ b/ref/harec/include/scope.h
@@ -0,0 +1,96 @@
+#ifndef HAREC_SCOPE_H
+#define HAREC_SCOPE_H
+#include "expr.h"
+#include "identifier.h"
+
+#define SCOPE_BUCKETS 4096
+
+enum object_type {
+ O_BIND,
+ O_CONST,
+ O_DECL,
+ O_SCAN,
+ O_TYPE,
+};
+
+enum scope_object_flags {
+ SO_THREADLOCAL = 1 << 0,
+ SO_FOR_EACH_SUBJECT = 1 << 1,
+};
+
+struct scope_object {
+ enum object_type otype;
+ // name is the name of the object within this scope (for lookups)
+ // ident is the global identifier (these may be different in some cases)
+ struct ident *name;
+ struct ident *ident;
+ enum scope_object_flags flags;
+
+ union {
+ const struct type *type;
+ struct expression *value; // For O_CONST
+ };
+ // Cannot be in union because type and idecl are needed at the same time
+ struct incomplete_decl *idecl;
+
+ struct scope_object *lnext; // Linked list
+ struct scope_object *mnext; // Hash map
+};
+
+enum scope_class {
+ SCOPE_COMPOUND,
+ SCOPE_DEFER,
+ SCOPE_ENUM,
+ SCOPE_FUNC,
+ SCOPE_LOOP,
+ SCOPE_MATCH,
+ SCOPE_SUBUNIT,
+ SCOPE_UNIT,
+ SCOPE_DEFINES,
+};
+
+struct yield { // and break
+ struct expression **expression;
+ struct yield *next;
+};
+
+struct scope {
+ enum scope_class class;
+ const char *label;
+ struct scope *parent;
+
+ const struct type *hint;
+ struct type_tagged_union results;
+ struct yield *yields;
+
+ // Linked list in insertion order
+ // Used for function parameters and enum values, where order matters
+ struct scope_object *objects;
+ struct scope_object **next;
+
+ // Hash map in reverse insertion order
+ // Used for lookups, and accounts for shadowing
+ struct scope_object *buckets[SCOPE_BUCKETS];
+};
+
+struct scopes {
+ struct scope *scope;
+ struct scopes *next;
+};
+
+struct scope *scope_push(struct scope **stack, enum scope_class class);
+struct scope *scope_pop(struct scope **stack);
+
+struct scope *scope_lookup_class(struct scope *scope, enum scope_class class);
+struct scope *scope_lookup_label(struct scope *scope, const char *label);
+
+void scope_free(struct scope *scope);
+void scope_free_all(struct scopes *scopes);
+
+struct scope_object *scope_insert(struct scope *scope,
+ enum object_type otype, struct ident *ident, struct ident *name,
+ const struct type *type, struct expression *value);
+
+struct scope_object *scope_lookup(struct scope *scope, struct ident *ident);
+
+#endif
diff --git a/ref/harec/include/type_store.h b/ref/harec/include/type_store.h
new file mode 100644
index 00000000..f57e5bc3
--- /dev/null
+++ b/ref/harec/include/type_store.h
@@ -0,0 +1,60 @@
+#ifndef HARE_TYPESTORE_H
+#define HARE_TYPESTORE_H
+#include "ast.h"
+#include "lex.h"
+#include "types.h"
+
+#define TYPE_STORE_BUCKETS 65536
+
+struct type_bucket {
+ struct type type;
+ struct type_bucket *next;
+};
+
+struct context;
+
+struct dimensions {
+ size_t size;
+ size_t align;
+};
+
+typedef struct type_bucket *type_store[TYPE_STORE_BUCKETS];
+
+// Applies the type reduction algorithm to the given tagged union.
+const struct type *type_store_reduce_result(struct context *ctx,
+ struct location loc, struct type_tagged_union *in);
+
+struct ast_type;
+
+const struct type *type_store_lookup_atype(
+ struct context *ctx, const struct ast_type *atype);
+
+struct dimensions type_store_lookup_dimensions(
+ struct context *ctx, const struct ast_type *atype);
+
+const struct type *builtin_type_for_storage(enum type_storage storage);
+
+const struct type *type_store_lookup_pointer(struct context *ctx,
+ struct location loc, const struct type *referent, bool nullable);
+
+const struct type *type_store_lookup_array(struct context *ctx,
+ struct location loc, const struct type *members, size_t len,
+ bool expandable);
+
+const struct type *type_store_lookup_slice(struct context *ctx,
+ struct location loc, const struct type *members);
+
+const struct type *type_store_lookup_alias(struct context *ctx,
+ struct ident *ident, struct ident *name,
+ const struct type *secondary, bool exported);
+
+const struct type *type_store_lookup_tagged(struct context *ctx,
+ struct location loc, struct type_tagged_union *tags);
+
+const struct type *type_store_lookup_tuple(struct context *ctx,
+ struct location loc, struct type_tuple *values);
+
+const struct type *type_store_lookup_enum(struct context *ctx,
+ const struct ast_type *atype, bool exported);
+
+#endif
diff --git a/ref/harec/include/typedef.h b/ref/harec/include/typedef.h
new file mode 100644
index 00000000..68e3c489
--- /dev/null
+++ b/ref/harec/include/typedef.h
@@ -0,0 +1,11 @@
+#ifndef HARE_TYPEDEF_H
+#define HARE_TYPEDEF_H
+#include
+
+struct type;
+struct unit;
+
+void emit_type(const struct type *type, FILE *out);
+void emit_typedefs(const struct unit *unit, FILE *out);
+
+#endif
diff --git a/ref/harec/include/types.h b/ref/harec/include/types.h
new file mode 100644
index 00000000..be3c5edb
--- /dev/null
+++ b/ref/harec/include/types.h
@@ -0,0 +1,230 @@
+#ifndef HARE_TYPES_H
+#define HARE_TYPES_H
+#include
+#include
+#include "arch.h"
+#include "identifier.h"
+
+enum type_storage {
+ // Built-in types
+ // The order of these is important
+ STORAGE_BOOL,
+ STORAGE_DONE,
+ STORAGE_F32,
+ STORAGE_F64,
+ STORAGE_I16,
+ STORAGE_I32,
+ STORAGE_I64,
+ STORAGE_I8,
+ STORAGE_INT,
+ STORAGE_NEVER,
+ STORAGE_NOMEM,
+ STORAGE_NULL,
+ STORAGE_OPAQUE,
+ STORAGE_RUNE,
+ STORAGE_SIZE,
+ STORAGE_STRING,
+ STORAGE_U16,
+ STORAGE_U32,
+ STORAGE_U64,
+ STORAGE_U8,
+ STORAGE_UINT,
+ STORAGE_UINTPTR,
+ STORAGE_UNDEFINED,
+ STORAGE_VOID,
+ // Other types
+ STORAGE_ALIAS,
+ STORAGE_ARRAY,
+ STORAGE_ENUM,
+ STORAGE_FUNCTION,
+ STORAGE_POINTER,
+ STORAGE_SLICE,
+ STORAGE_STRUCT,
+ STORAGE_TAGGED,
+ STORAGE_TUPLE,
+ STORAGE_UNION,
+ STORAGE_VALIST,
+ STORAGE_FCONST,
+ STORAGE_ICONST,
+ STORAGE_RCONST,
+ STORAGE_ERROR,
+ // For internal use only
+ STORAGE_INVALID,
+};
+
+struct context;
+struct type;
+
+#define SIZE_UNDEFINED ((size_t)-1)
+#define ALIGN_UNDEFINED ((size_t)-1)
+
+struct type_alias {
+ struct ident *ident;
+ struct ident *name;
+ const struct type *type;
+ bool exported; // Used to make sure unexported aliases aren't emitted
+};
+
+struct type_array {
+ size_t length; // SIZE_UNDEFINED for [*] and slices
+ const struct type *members;
+ bool expandable;
+};
+
+struct type_enum {
+ struct scope *values;
+};
+
+enum variadism {
+ VARIADISM_NONE,
+ VARIADISM_C,
+ VARIADISM_HARE,
+};
+
+struct type_func_param {
+ const struct type *type;
+ struct expression *default_value;
+ struct type_func_param *next;
+};
+
+struct type_func {
+ const struct type *result;
+ enum variadism variadism;
+ struct type_func_param *params;
+};
+
+struct type_flexible {
+ int64_t min, max;
+ uint32_t id;
+ const struct type ***refs;
+ size_t nrefs;
+ size_t zrefs;
+};
+
+struct type_pointer {
+ const struct type *referent;
+ bool nullable;
+};
+
+struct struct_field {
+ const char *name;
+ const struct type *type;
+ size_t offset;
+ size_t size;
+ struct struct_field *next;
+};
+
+struct type_struct_union {
+ struct struct_field *fields;
+ bool packed;
+};
+
+struct type_tuple {
+ const struct type *type;
+ size_t offset;
+ struct type_tuple *next;
+};
+
+struct type_tagged_union {
+ const struct type **types;
+ size_t len;
+ size_t cap;
+};
+
+struct type {
+ enum type_storage storage;
+ uint32_t id;
+ size_t size, align;
+ union {
+ struct {
+ struct type_alias alias;
+ struct type_enum _enum;
+ };
+ struct type_array array;
+ const struct type *error;
+ struct type_flexible flexible;
+ struct type_func func;
+ struct type_pointer pointer;
+ struct type_struct_union struct_union;
+ struct type_tagged_union tagged;
+ struct type_tuple tuple;
+ };
+};
+
+const struct type *type_dereference(struct context *ctx, const struct type *type,
+ bool allow_nullable);
+const struct type *type_dealias(struct context *ctx, const struct type *type);
+bool type_is_done(struct context *ctx, const struct type *type);
+const struct struct_field *type_get_field(struct context *ctx,
+ const struct type *type, const char *name);
+const struct type_tuple *type_get_value(
+ const struct type *type, uint64_t index);
+
+void tagged_append(struct type_tagged_union *tagged, const struct type *memb);
+struct type_tagged_union tagged_dup_tags(const struct type_tagged_union *tags);
+const struct type *tagged_select_subtype(struct context *ctx,
+ const struct type *tagged, const struct type *subtype, bool strip);
+bool tagged_subset_compat(struct context *ctx,
+ const struct type *to, const struct type *from);
+
+const char *type_storage_unparse(enum type_storage storage);
+bool type_is_signed(struct context *ctx, const struct type *type);
+bool type_is_integer(struct context *ctx, const struct type *type);
+bool type_is_numeric(struct context *ctx, const struct type *type);
+bool type_is_float(struct context *ctx, const struct type *type);
+bool type_is_flexible(const struct type *type);
+bool type_is_error(struct context *ctx, const struct type *type);
+bool type_has_error(struct context *ctx, const struct type *type);
+
+uint32_t type_hash(const struct type *type);
+bool type_equal(const struct type *a, const struct type *b);
+
+const struct type *promote_flexible(struct context *ctx,
+ const struct type *a, const struct type *b);
+bool type_is_assignable(struct context *ctx,
+ const struct type *to, const struct type *from);
+const struct type *type_is_castable(struct context *ctx,
+ const struct type *to, const struct type *from);
+
+const struct type *type_create_flexible(enum type_storage storage,
+ int64_t min, int64_t max);
+const struct type *lower_flexible(struct context *ctx,
+ const struct type *old, const struct type *new);
+void flexible_refer(const struct type *type, const struct type **ref);
+void flexible_reset_refs(const struct type *type);
+
+void builtin_types_init(enum arch target);
+
+// Built-in type singletons
+extern struct type
+ // Primitive
+ builtin_type_bool,
+ builtin_type_done,
+ builtin_type_invalid,
+ builtin_type_f32,
+ builtin_type_f64,
+ builtin_type_i16,
+ builtin_type_i32,
+ builtin_type_i64,
+ builtin_type_i8,
+ builtin_type_int,
+ builtin_type_never,
+ builtin_type_nomem,
+ builtin_type_null,
+ builtin_type_opaque,
+ builtin_type_rune,
+ builtin_type_size,
+ builtin_type_u16,
+ builtin_type_u32,
+ builtin_type_u64,
+ builtin_type_u8,
+ builtin_type_uint,
+ builtin_type_uintptr,
+ builtin_type_void,
+
+ // etc
+ builtin_type_str,
+ builtin_type_valist,
+ builtin_type_undefined;
+
+#endif
diff --git a/ref/harec/include/utf8.h b/ref/harec/include/utf8.h
new file mode 100644
index 00000000..7932aef4
--- /dev/null
+++ b/ref/harec/include/utf8.h
@@ -0,0 +1,26 @@
+#ifndef HAREC_UTF8_H
+#define HAREC_UTF8_H
+#include
+#include
+#include
+
+#define UTF8_MAX_SIZE 4
+
+#define UTF8_INVALID UINT32_MAX
+
+/**
+ * Grabs the next UTF-8 codepoint and advances the string pointer
+ */
+uint32_t utf8_decode(const char **str);
+
+/**
+ * Encodes a codepoint as UTF-8 and returns the length of that codepoint.
+ */
+size_t utf8_encode(char *str, uint32_t ch);
+
+/**
+ * Reads and returns the next codepoint from the file.
+ */
+uint32_t utf8_get(FILE *f);
+
+#endif
diff --git a/ref/harec/include/util.h b/ref/harec/include/util.h
new file mode 100644
index 00000000..c69d937a
--- /dev/null
+++ b/ref/harec/include/util.h
@@ -0,0 +1,57 @@
+#ifndef HARE_UTIL_H
+#define HARE_UTIL_H
+#include
+#include
+#include
+#include
+#include
+#include "lex.h"
+
+enum exit_status {
+ /* EXIT_SUCCESS = 0 (defined in stdlib.h) */
+ EXIT_USER = 1,
+ EXIT_LEX = 2,
+ EXIT_PARSE = 3,
+ EXIT_CHECK = 4,
+ EXIT_ABNORMAL = 255,
+};
+
+extern const char **sources;
+// Sources unaffected by the -M option
+extern const char **full_sources;
+extern size_t nsources;
+
+#define FNV1A_INIT 2166136261u
+
+uint32_t fnv1a(uint32_t hash, unsigned char c);
+uint32_t fnv1a_u32(uint32_t hash, uint32_t u32);
+uint32_t fnv1a_u64(uint32_t hash, uint64_t u64);
+uint32_t fnv1a_size(uint32_t hash, size_t sz);
+uint32_t fnv1a_s(uint32_t hash, const char *str);
+void *xcalloc(size_t n, size_t s);
+void *xrealloc(void *p, size_t s);
+char *xstrdup(const char *s);
+
+#define FORMAT(FMT_PARAM, VA_PARAM)
+#ifdef __has_attribute
+#if __has_attribute(format)
+#undef FORMAT
+#define FORMAT(FMT_PARAM, VA_PARAM) __attribute__((format(printf, FMT_PARAM, VA_PARAM)))
+#endif
+#endif
+
+int xfprintf(FILE *restrict f, const char *restrict fmt, ...) FORMAT(2, 3);
+int xvfprintf(FILE *restrict f, const char *restrict fmt, va_list ap) FORMAT(2, 0);
+
+#define malloc(a) (void *)sizeof(struct { static_assert(0, "Use xcalloc instead"); int _; })
+#define calloc(a, b) (void *)sizeof(struct { static_assert(0, "Use xcalloc instead"); int _; })
+#define realloc(a, b) (void *)sizeof(struct { static_assert(0, "Use xrealloc instead"); int _; })
+#define strdup(s) (char *)(sizeof(struct { static_assert(0, "Use xstrdup instead"); int _; })
+
+char *gen_name(int *id, const char *fmt);
+void append_buffer(char **buf, size_t *restrict ln, size_t *restrict cap,
+ const char *b, size_t sz);
+
+void errline(struct location loc);
+
+#endif
diff --git a/ref/harec/src/check.c b/ref/harec/src/check.c
new file mode 100644
index 00000000..1d47a8b4
--- /dev/null
+++ b/ref/harec/src/check.c
@@ -0,0 +1,5047 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "ast.h"
+#include "check.h"
+#include "eval.h"
+#include "expr.h"
+#include "identifier.h"
+#include "mod.h"
+#include "scope.h"
+#include "type_store.h"
+#include "typedef.h"
+#include "types.h"
+#include "util.h"
+
+struct ident *
+mkident(struct context *ctx, struct ident *in, const char *symbol)
+{
+ if (symbol) {
+ return intern_name(ctx->itbl, symbol);
+ } else if (ctx->ns && in->ns == NULL) {
+ return intern_ident(ctx->itbl, in->name, ctx->ns);
+ } else {
+ return in;
+ }
+}
+
+static struct ident *
+intern_generated(struct context *ctx, const char *template)
+{
+ const char *s = intern_owned(ctx->itbl, gen_name(&ctx->id, template));
+ return intern_name(ctx->itbl, s);
+}
+
+void
+mkstrliteral(struct expression *expr, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ size_t n = vsnprintf(NULL, 0, fmt, ap);
+ va_end(ap);
+ char *s = xcalloc(n + 1, n);
+ va_start(ap, fmt);
+ vsnprintf(s, n + 1, fmt, ap);
+ va_end(ap);
+
+ *expr = (struct expression) {
+ .type = EXPR_LITERAL,
+ .result = &builtin_type_str,
+ };
+ expr->literal.string.value = s;
+ expr->literal.string.len = n;
+}
+
+char *
+gen_typename(const struct type *type)
+{
+ size_t sz = 0;
+ char *ptr = NULL;
+ FILE *f = open_memstream(&ptr, &sz);
+ if (f == NULL) {
+ xfprintf(stderr, "Unable to open memstream: %s\n",
+ strerror(errno));
+ exit(EXIT_ABNORMAL);
+ }
+ emit_type(type, f);
+ fclose(f);
+ return ptr;
+}
+
+static void
+handle_errors(struct errors *errors)
+{
+ struct errors *error = errors;
+ while (error) {
+ xfprintf(stderr, "%s:%d:%d: error: %s\n", sources[error->loc.file],
+ error->loc.lineno, error->loc.colno, error->msg);
+ errline(error->loc);
+ free(error->msg);
+ struct errors *next = error->next;
+ free(error);
+ error = next;
+ }
+ if (errors) {
+ exit(EXIT_CHECK);
+ }
+}
+
+static void
+mkerror(struct expression *expr)
+{
+ expr->type = EXPR_LITERAL;
+ expr->result = &builtin_type_invalid;
+ expr->literal.uval = 0;
+ expr->loc = (struct location){0};
+}
+
+static void
+verror(struct context *ctx, const struct location loc,
+ const char *fmt, va_list ap)
+{
+ va_list copy;
+ va_copy(copy, ap);
+ size_t sz = vsnprintf(NULL, 0, fmt, copy);
+ va_end(copy);
+
+ char *msg = xcalloc(sz + 1, 1);
+ vsnprintf(msg, sz + 1, fmt, ap);
+
+ struct errors *next = *ctx->next = xcalloc(1, sizeof(struct errors));
+ next->loc = loc;
+ next->msg = msg;
+ ctx->next = &next->next;
+}
+
+void
+error(struct context *ctx, struct location loc, struct expression *expr,
+ const char *fmt, ...)
+{
+ if (expr) {
+ mkerror(expr);
+ expr->loc = loc;
+ }
+ va_list ap;
+ va_start(ap, fmt);
+ verror(ctx, loc, fmt, ap);
+ va_end(ap);
+}
+
+FORMAT(3, 4) static noreturn void
+error_norec(struct context *ctx, struct location loc, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ verror(ctx, loc, fmt, ap);
+ va_end(ap);
+
+ handle_errors(ctx->errors);
+ abort();
+}
+
+struct expression *
+lower_implicit_cast(struct context *ctx,
+ const struct type *to, struct expression *expr)
+{
+ if (to == expr->result || expr->result->storage == STORAGE_NEVER) {
+ return expr;
+ }
+
+ if (type_dealias(ctx, to)->storage == STORAGE_TAGGED) {
+ const struct type *interim =
+ tagged_select_subtype(ctx, to, expr->result, true);
+ if (interim) {
+ expr = lower_implicit_cast(ctx, interim, expr);
+ }
+ }
+
+ struct expression *cast = xcalloc(1, sizeof(struct expression));
+ cast->type = EXPR_CAST;
+ cast->loc = expr->loc;
+ cast->result = cast->cast.secondary = to;
+ cast->cast.kind = C_CAST;
+ cast->cast.value = expr;
+ cast->cast.lowered = true;
+ return cast;
+}
+
+static void resolve_decl(struct context *ctx, struct scope_object *obj);
+
+static const struct type *
+check_autodereference(struct context *ctx, struct location loc,
+ const struct type *type)
+{
+ const struct type *dtype = type_dereference(ctx, type, false);
+ if (dtype == NULL) {
+ error(ctx, loc, NULL, "Cannot autodereference a nullable pointer");
+ }
+ return type_dereference(ctx, type, true);
+}
+
+struct match_context {
+ struct context *ctx;
+ // Type of the object being matched
+ const struct type *otype;
+ // Type the object type refers to, in the case of a pointer
+ const struct type *ref_type;
+ // Derived from otype
+ bool is_tagged, is_nullable_ptr, is_tagged_ptr;
+};
+
+// Returns false if an error occured
+static bool
+begin_check_match(struct context *ctx,
+ struct match_context *mctx,
+ struct expression *expr,
+ const struct type *otype,
+ struct location oloc)
+{
+ mctx->ctx = ctx;
+ mctx->otype = otype;
+
+ const struct type *type = type_dealias(ctx, otype);
+ if (type->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return false;
+ }
+
+ mctx->is_tagged = type->storage == STORAGE_TAGGED;
+ mctx->is_nullable_ptr = false;
+ mctx->is_tagged_ptr = false;
+ mctx->ref_type = NULL;
+ if (type->storage == STORAGE_POINTER) {
+ mctx->is_nullable_ptr = type->pointer.nullable;
+ mctx->ref_type = type_dealias(ctx, type->pointer.referent);
+ if (mctx->ref_type->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return false;
+ }
+ mctx->is_tagged_ptr = mctx->ref_type->storage == STORAGE_TAGGED;
+
+ }
+ if (!mctx->is_tagged && !mctx->is_nullable_ptr && !mctx->is_tagged_ptr) {
+ error(ctx, oloc, expr,
+ "Value is not tagged union, pointer to tagged union, or nullable pointer type");
+ return false;
+ }
+ return true;
+}
+
+static const char *
+check_match_case_nullable_ptr(struct match_context *mctx, const struct type *ctype)
+{
+ // match (e: nullable *ref_type) {
+ // case ctype =>
+ // Null has already been handled.
+ if (ctype->storage != STORAGE_POINTER) {
+ return "Match on nullable pointer: case is not null or pointer type";
+ } else if (mctx->ref_type != type_dealias(mctx->ctx, ctype->pointer.referent)) {
+ return "Match on nullable pointer: case has invalid pointer type";
+ }
+ return NULL;
+}
+
+static const char *
+check_match_case_tagged(struct match_context *mctx, const struct type *ctype)
+{
+ const struct type *type = type_dealias(mctx->ctx, mctx->otype);
+ // match (e: type) {
+ // case ctype =>
+ // TODO: Assign a score to tagged compatibility
+ // and choose the branch with the highest score.
+ if (!type_is_assignable(mctx->ctx, type, ctype)) {
+ return "Match on tagged union: case is not assignable to match type";
+ }
+ return NULL;
+}
+
+static const char *
+check_match_case_tagged_ptr(struct match_context *mctx, const struct type *ctype)
+{
+ // match (e: *ref_type) {
+ // case ctype =>
+ if (ctype->size == 0) {
+ if (!type_is_assignable(mctx->ctx, mctx->ref_type, ctype)) {
+ return "Match on pointer to tagged union: zero-sized case type is not assignable to match type";
+ }
+ } else if (ctype->storage == STORAGE_NULL) {
+ // XXX: The purpose of this branch is to prevent `x as null`.
+ // Should we allow that, though?
+ return "Cannot match with null in this context";
+ } else if (ctype->storage != STORAGE_POINTER) {
+ return "Match on pointer to tagged union: finite-sized case type is not a pointer";
+ } else if (!type_is_assignable(mctx->ctx, mctx->ref_type, ctype->pointer.referent)) {
+ return "Match on pointer to tagged union: case is not assignable to match type";
+ }
+ return NULL;
+}
+
+static bool
+check_match_case(struct match_context *mctx,
+ const struct type *ctype,
+ struct expression *expr,
+ struct location loc)
+{
+ const char *err_msg = NULL;
+
+ if (ctype->storage == STORAGE_NULL && mctx->is_nullable_ptr) {
+ // Ok in all cases.
+ } else if (mctx->is_nullable_ptr && !mctx->is_tagged_ptr) {
+ err_msg = check_match_case_nullable_ptr(mctx, ctype);
+ } else if (mctx->is_tagged_ptr) {
+ err_msg = check_match_case_tagged_ptr(mctx, ctype);
+ } else {
+ assert(mctx->is_tagged);
+ err_msg = check_match_case_tagged(mctx, ctype);
+ }
+
+ if (err_msg) {
+ error(mctx->ctx, loc, expr, "%s", err_msg);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+check_expr_access(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_ACCESS;
+ expr->access.type = aexpr->access.type;
+
+ struct scope_object *obj = NULL;
+ switch (expr->access.type) {
+ case ACCESS_IDENTIFIER:
+ obj = scope_lookup(ctx->scope, aexpr->access.ident);
+ if (!obj) {
+ char buf[IDENT_BUFSIZ];
+ ident_unparse_static(aexpr->access.ident, buf);
+ error(ctx, aexpr->loc, expr,
+ "Unknown object '%s'", buf);
+ return;
+ }
+ wrap_resolver(ctx, obj, resolve_decl);
+
+ switch (obj->otype) {
+ case O_CONST:
+ // Lower flexible types
+ *expr = *obj->value;
+ expr->loc = aexpr->loc;
+ flexible_reset_refs(expr->result);
+ break;
+ case O_BIND:
+ case O_DECL:
+ expr->result = obj->type;
+ expr->access.object = obj;
+ break;
+ case O_TYPE:
+ if (type_dealias(ctx, obj->type)->storage != STORAGE_VOID &&
+ type_dealias(ctx, obj->type)->storage != STORAGE_DONE) {
+ char *ident = ident_unparse(obj->type->alias.ident);
+ error(ctx, aexpr->loc, expr,
+ "Cannot use non void or done type alias '%s' as literal",
+ ident);
+ free(ident);
+ return;
+ }
+ expr->type = EXPR_LITERAL;
+ expr->result = obj->type;
+ break;
+ case O_SCAN:
+ assert(0); // handled above
+ }
+ break;
+ case ACCESS_INDEX:
+ expr->access.array = xcalloc(1, sizeof(struct expression));
+ expr->access.index = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->access.array, expr->access.array, NULL);
+ check_expression(ctx, aexpr->access.index, expr->access.index, &builtin_type_size);
+ const struct type *atype = check_autodereference(ctx,
+ aexpr->access.array->loc, expr->access.array->result);
+ atype = type_dealias(ctx, atype);
+ if (atype->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return;
+ }
+ const struct type *itype =
+ type_dealias(ctx, expr->access.index->result);
+ if (atype->storage != STORAGE_ARRAY
+ && atype->storage != STORAGE_SLICE) {
+ error(ctx, aexpr->access.array->loc, expr,
+ "Can only index into array or slice object, but got %s",
+ type_storage_unparse(atype->storage));
+ return;
+ }
+ if (atype->storage == STORAGE_SLICE
+ && atype->array.members->size == SIZE_UNDEFINED) {
+ error(ctx, aexpr->access.array->loc, expr,
+ "Cannot use index into slice whose member type has undefined size");
+ return;
+ }
+ if (!type_is_integer(ctx, itype)) {
+ error(ctx, aexpr->access.index->loc, expr,
+ "Cannot use non-integer %s type as slice/array index",
+ type_storage_unparse(itype->storage));
+ return;
+ }
+ expr->access.index = lower_implicit_cast(ctx,
+ &builtin_type_size, expr->access.index);
+ expr->result = atype->array.members;
+
+ // Compile-time bounds check
+ if (atype->storage == STORAGE_ARRAY
+ && atype->array.length != SIZE_UNDEFINED) {
+ struct expression *evaled = xcalloc(1, sizeof(struct expression));
+ if (eval_expr(ctx, expr->access.index, evaled)) {
+ if (evaled->literal.uval >= atype->array.length) {
+ error(ctx, aexpr->loc, expr,
+ "Index must be less than array length");
+ free(evaled);
+ return;
+ }
+ expr->access.bounds_checked = true;
+ }
+ free(evaled);
+ }
+
+ break;
+ case ACCESS_FIELD:
+ expr->access._struct = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->access._struct, expr->access._struct, NULL);
+ const struct type *stype = check_autodereference(ctx,
+ aexpr->access._struct->loc, expr->access._struct->result);
+ stype = type_dealias(ctx, stype);
+ if (stype->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return;
+ }
+ if (stype->storage != STORAGE_STRUCT
+ && stype->storage != STORAGE_UNION) {
+ error(ctx, aexpr->access._struct->loc, expr,
+ "Cannot select field from non-struct, non-union object");
+ return;
+ }
+ expr->access.field = type_get_field(ctx, stype, aexpr->access.field);
+ if (!expr->access.field) {
+ error(ctx, aexpr->access._struct->loc, expr,
+ "No such struct field '%s'", aexpr->access.field);
+ return;
+ }
+ expr->result = expr->access.field->type;
+ break;
+ case ACCESS_TUPLE:
+ expr->access.tuple = xcalloc(1, sizeof(struct expression));
+ struct expression *value = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->access.tuple, expr->access.tuple, NULL);
+ check_expression(ctx, aexpr->access.value, value, NULL);
+ assert(value->type == EXPR_LITERAL);
+
+ const struct type *ttype = check_autodereference(ctx,
+ aexpr->access.tuple->loc, expr->access.tuple->result);
+ ttype = type_dealias(ctx, ttype);
+ if (ttype->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return;
+ }
+ if (ttype->storage != STORAGE_TUPLE) {
+ error(ctx, aexpr->access.tuple->loc, expr,
+ "Cannot select value from non-tuple object");
+ return;
+ }
+ if (!type_is_integer(ctx, value->result)) {
+ error(ctx, aexpr->access.tuple->loc, expr,
+ "Cannot use non-integer literal to select tuple value");
+ return;
+ }
+
+ expr->access.tvalue = type_get_value(ttype,
+ aexpr->access.value->literal.uval);
+ if (!expr->access.tvalue) {
+ error(ctx, aexpr->access.tuple->loc, expr,
+ "No such tuple value '%" PRIu64 "'",
+ aexpr->access.value->literal.uval);
+ return;
+ }
+ expr->access.tindex = aexpr->access.value->literal.uval;
+
+ expr->result = expr->access.tvalue->type;
+ break;
+ }
+}
+
+static void
+check_expr_alloc_init(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *inithint,
+ bool nullable)
+{
+ // alloc(initializer) case
+ check_expression(ctx, aexpr->alloc.init, expr->alloc.init, inithint);
+ if (expr->alloc.init->result->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return;
+ }
+
+ const struct type *objtype = expr->alloc.init->result;
+ if (objtype->storage == STORAGE_UNDEFINED) {
+ if (!inithint) {
+ error(ctx, aexpr->loc, expr, "Cannot infer @undefined type without type hint");
+ return;
+ }
+ objtype = inithint;
+ }
+
+ if (type_dealias(ctx, objtype)->storage == STORAGE_ARRAY
+ && type_dealias(ctx, objtype)->array.expandable) {
+ const struct type *atype = type_dealias(ctx, objtype);
+ if (!inithint) {
+ error(ctx, aexpr->loc, expr,
+ "Cannot infer expandable array length without type hint");
+ return;
+ }
+ const struct type *htype = type_dealias(ctx, inithint);
+ if (htype->storage != STORAGE_ARRAY) {
+ error(ctx, aexpr->loc, expr,
+ "Cannot assign expandable array from non-array type");
+ return;
+ }
+ assert(htype->array.members == atype->array.members);
+ objtype = inithint;
+ }
+ if (type_is_flexible(objtype) && inithint) {
+ const struct type *promoted =
+ promote_flexible(ctx, objtype, inithint);
+ if (promoted) {
+ objtype = promoted;
+ }
+ } else if (inithint) {
+ uint32_t objtype_id = type_dealias(ctx, objtype)->id;
+ uint32_t inithint_id = type_dealias(ctx, inithint)->id;
+ if (objtype_id == inithint_id) {
+ objtype = inithint;
+ }
+ }
+
+ expr->alloc.allocation_result = type_store_lookup_pointer(ctx,
+ aexpr->loc, objtype, nullable);
+
+ const struct type *initresult = expr->alloc.init->result;
+ if (initresult->storage != STORAGE_UNDEFINED
+ && initresult->size == SIZE_UNDEFINED) {
+ error(ctx, aexpr->loc, expr,
+ "Cannot allocate object of undefined size");
+ return;
+ }
+}
+
+static void
+check_expr_alloc_cap(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *inithint)
+{
+ // alloc(init, length/capacity) case
+ check_expression(ctx, aexpr->alloc.init, expr->alloc.init, inithint);
+ if (expr->alloc.init->result->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return;
+ }
+
+ const struct type *objtype = expr->alloc.init->result;
+ if (type_dealias(ctx, objtype)->storage == STORAGE_ARRAY) {
+ if (type_dealias(ctx, objtype)->array.length == SIZE_UNDEFINED) {
+ error(ctx, aexpr->alloc.init->loc, expr,
+ "Slice initializer must have defined length");
+ return;
+ }
+ } else if (type_dealias(ctx, objtype)->storage != STORAGE_SLICE) {
+ error(ctx, aexpr->alloc.init->loc, expr,
+ "Slice initializer must be of slice or array type, not %s",
+ type_storage_unparse(type_dealias(ctx, objtype)->storage));
+ return;
+ }
+
+ const struct type *caphint = &builtin_type_size;
+ expr->alloc.cap = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->alloc.cap, expr->alloc.cap, caphint);
+
+ const struct type *captype = expr->alloc.cap->result;
+ if (!type_is_assignable(ctx, &builtin_type_size, captype)) {
+ error(ctx, aexpr->alloc.cap->loc, expr,
+ "Slice capacity must be assignable to size");
+ return;
+ }
+ expr->alloc.cap = lower_implicit_cast(ctx, &builtin_type_size, expr->alloc.cap);
+
+ struct expression cap = {0};
+ if (expr->alloc.init->type == EXPR_LITERAL
+ && expr->alloc.cap->type == EXPR_LITERAL
+ && eval_expr(ctx, expr->alloc.cap, &cap)) {
+ uint64_t len = 0;
+ for (struct array_literal *c = expr->alloc.init->literal.array;
+ c != NULL; c = c->next) {
+ len++;
+ }
+ if (cap.literal.uval < len) {
+ error(ctx, aexpr->alloc.cap->loc, expr,
+ "Slice capacity cannot be smaller than length of initializer");
+ return;
+ }
+ }
+
+ const struct type *membtype = type_dealias(ctx, objtype)->array.members;
+ expr->alloc.allocation_result = type_store_lookup_slice(ctx,
+ aexpr->alloc.init->loc, membtype);
+
+ if (objtype->storage == STORAGE_ARRAY
+ && objtype->array.expandable) {
+ expr->alloc.kind = ALLOC_LEN;
+ }
+}
+
+static void
+check_expr_alloc_copy(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *inithint)
+{
+ // alloc(init...) case
+ check_expression(ctx, aexpr->alloc.init, expr->alloc.init, inithint);
+ if (expr->alloc.init->result->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return;
+ }
+
+ const struct type *result = type_dealias(ctx, expr->alloc.init->result);
+ if (result->storage != STORAGE_ARRAY
+ && result->storage != STORAGE_SLICE) {
+ error(ctx, aexpr->alloc.init->loc, expr,
+ "Slice initializer must be of slice or array type, not %s",
+ type_storage_unparse(result->storage));
+ return;
+ }
+ if (result->storage == STORAGE_ARRAY) {
+ if (result->array.expandable) {
+ error(ctx, aexpr->alloc.init->loc, NULL,
+ "Slice initializer can't be an expandable array");
+ } else if (result->array.length == SIZE_UNDEFINED) {
+ error(ctx, aexpr->alloc.init->loc, NULL,
+ "Slice initializer can't be an unbounded array");
+ }
+ // can recover from error
+ }
+
+ result = type_dealias(ctx, expr->alloc.init->result);
+ expr->alloc.allocation_result = type_store_lookup_slice(ctx,
+ aexpr->alloc.init->loc, result->array.members);
+}
+
+static void
+alloc_inithint(struct context *ctx,
+ const struct type *hint,
+ enum alloc_kind kind,
+ const struct type **inithint,
+ bool *nullable)
+{
+ const struct type *htype = NULL;
+ hint = type_dealias(ctx, hint);
+
+ switch (hint->storage) {
+ case STORAGE_TAGGED:
+ if (hint->tagged.len != 2) {
+ *inithint = NULL;
+ return;
+ }
+
+ if (hint->tagged.types[0] == &builtin_type_nomem) {
+ htype = hint->tagged.types[1];
+ } else if (hint->tagged.types[1] == &builtin_type_nomem) {
+ htype = hint->tagged.types[0];
+ } else {
+ *inithint = NULL;
+ return;
+ }
+ break;
+ case STORAGE_POINTER:
+ case STORAGE_SLICE:
+ // handle cases such as
+ // let a: alloc(0) as *u8;
+ // let b: []u8 = alloc([0])!;
+ htype = hint;
+ break;
+ default:
+ *inithint = NULL;
+ return;
+ }
+
+ switch (htype->storage) {
+ case STORAGE_POINTER:
+ if (kind == ALLOC_OBJECT) {
+ *inithint = htype->pointer.referent;
+ *nullable = htype->pointer.nullable;
+ }
+ break;
+ case STORAGE_SLICE:
+ *inithint = hint;
+ break;
+ default:
+ *inithint = NULL;
+ return;
+ };
+}
+
+static void
+check_expr_alloc(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ assert(aexpr->type == EXPR_ALLOC);
+ expr->type = EXPR_ALLOC;
+ expr->result = &builtin_type_void;
+ expr->alloc.init = xcalloc(1, sizeof(struct expression));
+ expr->alloc.kind = aexpr->alloc.kind;
+
+ const struct type *inithint = NULL;
+ bool nullable = false;
+
+ if (hint != NULL) {
+ alloc_inithint(ctx, hint, expr->alloc.kind, &inithint, &nullable);
+ }
+
+ switch (aexpr->alloc.kind) {
+ case ALLOC_OBJECT:
+ check_expr_alloc_init(ctx, aexpr, expr, inithint, nullable);
+ break;
+ case ALLOC_CAP:
+ check_expr_alloc_cap(ctx, aexpr, expr, inithint);
+ break;
+ case ALLOC_COPY:
+ check_expr_alloc_copy(ctx, aexpr, expr, inithint);
+ break;
+ case ALLOC_LEN:
+ abort(); // Not determined by parse
+ }
+
+ if (expr->result == &builtin_type_invalid) {
+ return;
+ }
+
+ const struct type *tags[] = { &builtin_type_nomem, expr->alloc.allocation_result };
+ struct type_tagged_union tagged = { .types = tags, .len = 2 };
+ expr->result = type_store_lookup_tagged(ctx, aexpr->loc, &tagged);
+}
+
+static void
+check_expr_append_insert(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ assert(aexpr->type == EXPR_APPEND || aexpr->type == EXPR_INSERT);
+ expr->type = aexpr->type;
+
+ const struct type *tags[] = { &builtin_type_nomem, &builtin_type_void };
+ struct type_tagged_union tagged = { .types = tags, .len = 2 };
+ expr->result = type_store_lookup_tagged(ctx, aexpr->loc, &tagged);
+
+ expr->append.is_static = aexpr->append.is_static;
+ expr->append.is_multi = aexpr->append.is_multi;
+ expr->append.object = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->append.object, expr->append.object, NULL);
+ if (expr->append.object->result->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return;
+ }
+ if (expr->append.object->type != EXPR_ACCESS) {
+ error(ctx, aexpr->append.object->loc, expr,
+ "Expression must operate on an object");
+ return;
+ }
+
+ const struct type *sltype;
+ const struct type *sltypename;
+ const char *exprtype_name;
+ struct expression *object = NULL;
+ switch (expr->type) {
+ case EXPR_APPEND:
+ sltypename = expr->append.object->result;
+ exprtype_name = "append";
+
+ object = expr->append.object;
+ break;
+ case EXPR_INSERT:
+ assert(expr->append.object->type == EXPR_ACCESS);
+ assert(expr->append.object->access.type == ACCESS_INDEX);
+ sltypename = expr->append.object->access.array->result;
+ exprtype_name = "insert";
+
+ object = expr->append.object->access.array;
+ break;
+ default:
+ abort(); // Invariant
+ }
+
+ if (object->type == EXPR_ACCESS
+ && object->access.type == ACCESS_IDENTIFIER
+ && object->access.object->flags &
+ SO_FOR_EACH_SUBJECT) {
+ error(ctx, aexpr->append.object->loc, expr,
+ "cannot %s the subject of for-each loop", exprtype_name);
+ }
+ sltype = check_autodereference(ctx, aexpr->append.object->loc, sltypename);
+ sltype = type_dealias(ctx, sltype);
+
+ if (sltype->storage != STORAGE_SLICE) {
+ char *typename = gen_typename(sltypename);
+ error(ctx, aexpr->append.object->loc, expr,
+ "%s expression must operate on a slice, but got %s",
+ exprtype_name, typename);
+ free(typename);
+ return;
+ }
+ if (sltype->array.members->size == SIZE_UNDEFINED) {
+ error(ctx, aexpr->append.object->loc, expr,
+ "Cannot %s %sto slice whose member type has undefined size",
+ exprtype_name, expr->type == EXPR_APPEND ? "" : "in");
+ return;
+ }
+
+ expr->append.value = xcalloc(1, sizeof(struct expression));
+
+ if (!expr->append.is_multi && !aexpr->append.length) {
+ check_expression(ctx, aexpr->append.value, expr->append.value,
+ sltype->array.members);
+ if (!type_is_assignable(ctx, sltype->array.members,
+ expr->append.value->result)) {
+ error(ctx, aexpr->append.value->loc, expr,
+ "Value type must be assignable to object member type");
+ return;
+ }
+ expr->append.value = lower_implicit_cast(ctx,
+ sltype->array.members, expr->append.value);
+ return;
+ }
+
+ check_expression(ctx, aexpr->append.value, expr->append.value, sltype);
+ const struct type *valtype = type_dealias(ctx, expr->append.value->result);
+ if (aexpr->append.length) {
+ if (valtype->storage != STORAGE_ARRAY
+ || !valtype->array.expandable) {
+ error(ctx, aexpr->append.value->loc, expr,
+ "Value must be an expandable array in append with length");
+ return;
+ }
+ struct expression *len = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->append.length, len, &builtin_type_size);
+ if (!type_is_assignable(ctx, &builtin_type_size, len->result)) {
+ error(ctx, aexpr->append.length->loc, expr,
+ "Length parameter must be assignable to size");
+ return;
+ }
+ len = lower_implicit_cast(ctx, &builtin_type_size, len);
+ expr->append.length = len;
+ } else if (valtype->storage != STORAGE_SLICE
+ && valtype->storage != STORAGE_ARRAY) {
+ error(ctx, aexpr->append.value->loc, expr,
+ "Value must be an array or a slice in multi-valued %s",
+ exprtype_name);
+ return;
+ } else if (valtype->size == SIZE_UNDEFINED) {
+ error(ctx, aexpr->loc, expr, "Value array must be bounded");
+ return;
+ }
+ if (sltype->array.members != valtype->array.members) {
+ error(ctx, aexpr->loc, expr,
+ "Value member type must match object member type");
+ return;
+ }
+}
+
+static void
+check_assert(struct context *ctx,
+ struct ast_expression_assert e,
+ struct location loc,
+ struct expression *expr)
+{
+ expr->result = &builtin_type_void;
+ expr->type = EXPR_ASSERT;
+
+ if (e.cond != NULL) {
+ expr->assert.cond = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, e.cond, expr->assert.cond, &builtin_type_bool);
+ loc = e.cond->loc;
+ if (expr->assert.cond->result->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return;
+ }
+ if (type_dealias(ctx, expr->assert.cond->result)->storage != STORAGE_BOOL) {
+ error(ctx, loc, expr, "Assertion condition must be boolean");
+ return;
+ }
+ } else {
+ if (!e.is_static) {
+ expr->result = &builtin_type_never;
+ }
+ }
+ if (e.message == NULL) {
+ expr->assert.fixed_reason = ABORT_ANON_ASSERTION_FAILED;
+ } else {
+ expr->assert.message = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, e.message, expr->assert.message, &builtin_type_str);
+ if (type_dealias(ctx, expr->assert.message->result)->storage != STORAGE_STRING) {
+ error(ctx, e.message->loc, expr,
+ "Assertion message must be string");
+ return;
+ }
+ }
+
+ if (e.is_static) {
+ expr->type = EXPR_LITERAL;
+ bool cond = false;
+ if (expr->assert.cond != NULL) {
+ struct expression out = {0}, msgout = {0};
+ if (!eval_expr(ctx, expr->assert.cond, &out)) {
+ error(ctx, e.cond->loc, expr,
+ "Unable to evaluate static assertion condition at compile time");
+ return;
+ }
+ if (expr->assert.message) {
+ if (!eval_expr(ctx, expr->assert.message, &msgout)) {
+ error(ctx, e.message->loc, expr,
+ "Unable to evaluate static assertion message at compile time");
+ return;
+ }
+ }
+ assert(type_dealias(ctx, out.result)->storage == STORAGE_BOOL);
+ cond = out.literal.bval;
+ }
+ // XXX: Should these abort immediately?
+ if (!cond) {
+ if (e.message != NULL) {
+ error(ctx, loc, expr, "Static assertion failed: %.*s",
+ (int)expr->assert.message->literal.string.len,
+ expr->assert.message->literal.string.value);
+ } else {
+ error(ctx, loc, expr, "Static assertion failed");
+ }
+ }
+ }
+}
+
+static void
+check_expr_assert(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ check_assert(ctx, aexpr->assert, aexpr->loc, expr);
+}
+
+static void
+check_binarithm_op(struct context *ctx, struct expression *expr,
+ enum binarithm_operator op)
+{
+ const struct type *dealiased = type_dealias(ctx, expr->result);
+ switch (op) {
+ // Numeric arithmetic
+ case BIN_DIV:
+ case BIN_MINUS:
+ case BIN_PLUS:
+ case BIN_TIMES:
+ if (!type_is_numeric(ctx, dealiased)) {
+ error(ctx, expr->loc, expr,
+ "Cannot perform arithmetic on non-numeric %s type",
+ type_storage_unparse(dealiased->storage));
+ }
+ return;
+ // Integer artithmetic
+ case BIN_BAND:
+ case BIN_BOR:
+ case BIN_LSHIFT:
+ case BIN_MODULO:
+ case BIN_RSHIFT:
+ case BIN_BXOR:
+ if (!type_is_integer(ctx, dealiased)) {
+ error(ctx, expr->loc, expr,
+ "Cannot perform operation on non-integer %s type",
+ type_storage_unparse(dealiased->storage));
+ }
+ return;
+ // Logical arithmetic
+ case BIN_LAND:
+ case BIN_LOR:
+ case BIN_LXOR:
+ expr->result = &builtin_type_bool;
+ if (dealiased->storage != STORAGE_BOOL) {
+ error(ctx, expr->loc, expr,
+ "Cannot perform logical arithmetic on non-bool %s type",
+ type_storage_unparse(dealiased->storage));
+ }
+ return;
+ case BIN_GREATER:
+ case BIN_GREATEREQ:
+ case BIN_LESS:
+ case BIN_LESSEQ:
+ expr->result = &builtin_type_bool;
+ if (!type_is_numeric(ctx, dealiased)) {
+ error(ctx, expr->loc, expr,
+ "Cannot perform comparison on non-numeric %s type",
+ type_storage_unparse(dealiased->storage));
+ }
+ return;
+ case BIN_LEQUAL:
+ case BIN_NEQUAL:
+ expr->result = &builtin_type_bool;
+ if (!type_is_numeric(ctx, dealiased) &&
+ dealiased->storage != STORAGE_POINTER
+ && dealiased->storage != STORAGE_STRING
+ && dealiased->storage != STORAGE_BOOL
+ && dealiased->storage != STORAGE_RCONST
+ && dealiased->storage != STORAGE_RUNE) {
+ error(ctx, expr->loc, expr,
+ "Cannot perform equality test on %s type",
+ type_storage_unparse(dealiased->storage));
+ }
+ return;
+ }
+}
+
+static void
+check_expr_assign(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_ASSIGN;
+ expr->result = &builtin_type_void;
+ expr->assign.op = aexpr->assign.op;
+
+ struct expression *value = xcalloc(1, sizeof(struct expression));
+
+ if (aexpr->assign.object == NULL) {
+ assert(expr->assign.op == BIN_LEQUAL);
+ check_expression(ctx, aexpr->assign.value, value, NULL);
+ expr->assign.value = value;
+ return;
+ }
+
+ struct expression *object = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->assign.object, object, NULL);
+ check_expression(ctx, aexpr->assign.value, value, object->result);
+
+ if (object->type == EXPR_LITERAL
+ && object->result != &builtin_type_invalid) {
+ error(ctx, aexpr->assign.object->loc, expr,
+ "Cannot assign to constant");
+ return;
+ }
+ if (object->result->size == SIZE_UNDEFINED) {
+ error(ctx, aexpr->loc, expr,
+ "Cannot assign to object with undefined size");
+ return;
+ }
+ if (!type_is_assignable(ctx, object->result, value->result)) {
+ char *valtypename = gen_typename(value->result);
+ char *objtypename = gen_typename(object->result);
+ error(ctx, aexpr->loc, expr,
+ "rvalue type (%s) is not assignable to lvalue (%s)",
+ valtypename, objtypename);
+ free(valtypename);
+ free(objtypename);
+ return;
+ }
+ if (expr->assign.op != BIN_LEQUAL) {
+ check_binarithm_op(ctx, object, expr->assign.op);
+ }
+
+ if (object->type == EXPR_SLICE
+ && value->result->storage == STORAGE_ARRAY
+ && value->result->array.expandable) {
+ expr->assign.value = value;
+ } else {
+ expr->assign.value =
+ lower_implicit_cast(ctx, object->result, value);
+ }
+ expr->assign.object = object;
+}
+
+static const struct type *
+type_promote(struct context *ctx, const struct type *a, const struct type *b)
+{
+ // Note: we must return either a, b, or NULL
+
+ if (a == b) {
+ return a;
+ }
+
+ if (a->storage == STORAGE_ALIAS && b->storage == STORAGE_ALIAS) {
+ return NULL;
+ }
+
+ // TODO: There are likely some improperly handled edge cases around type
+ // flags, both here and in the spec
+ const struct type *da = type_dealias(ctx, a);
+ const struct type *db = type_dealias(ctx, b);
+ if (da == db) {
+ if (a->storage == STORAGE_ALIAS) {
+ return a;
+ } else if (b->storage == STORAGE_ALIAS) {
+ return b;
+ } else if (a->storage == STORAGE_ERROR) {
+ return a;
+ } else {
+ return b;
+ }
+ }
+
+ if (type_is_flexible(da) || type_is_flexible(db)) {
+ return promote_flexible(ctx, a, b);
+ }
+
+ if (db->storage == STORAGE_ENUM && da->storage == db->alias.type->storage) {
+ return b;
+ }
+
+ if (db->storage == STORAGE_INVALID) {
+ return a;
+ }
+
+ switch (da->storage) {
+ case STORAGE_ENUM:
+ if (da->alias.type->storage == db->storage) {
+ return a;
+ }
+ return NULL;
+ case STORAGE_I8:
+ case STORAGE_I16:
+ case STORAGE_I32:
+ case STORAGE_I64:
+ case STORAGE_INT:
+ if (!type_is_integer(ctx, db) || !type_is_signed(ctx, db)
+ || db->size == da->size) {
+ return NULL;
+ }
+ return da->size > db->size ? a : b;
+ case STORAGE_U32:
+ case STORAGE_U16:
+ case STORAGE_U64:
+ case STORAGE_UINT:
+ case STORAGE_SIZE:
+ case STORAGE_U8:
+ if (da->storage == STORAGE_SIZE && db->storage == STORAGE_UINTPTR) {
+ return db;
+ }
+ if (!type_is_integer(ctx, db) || type_is_signed(ctx, db)
+ || db->size == da->size) {
+ return NULL;
+ }
+ return da->size > db->size ? a : b;
+ case STORAGE_F32:
+ case STORAGE_F64:
+ if (!type_is_float(ctx, db) || db->size == da->size) {
+ return NULL;
+ }
+ return da->size > db->size ? a : b;
+ case STORAGE_POINTER:
+ if (db->storage == STORAGE_NULL) {
+ return a;
+ }
+ if (db->storage == STORAGE_UINTPTR) {
+ return a;
+ }
+ if (db->storage != STORAGE_POINTER) {
+ return NULL;
+ }
+ if (da->pointer.referent->storage == STORAGE_OPAQUE ||
+ db->pointer.referent->storage == STORAGE_OPAQUE) {
+ return a;
+ }
+ const struct type *r = type_promote(ctx,
+ da->pointer.referent, db->pointer.referent);
+ if (r == da->pointer.referent) {
+ return a;
+ }
+ if (r == db->pointer.referent) {
+ return b;
+ }
+ assert(r == NULL);
+ return NULL;
+ case STORAGE_NULL:
+ if (db->storage == STORAGE_POINTER
+ || db->storage == STORAGE_UINTPTR) {
+ return b;
+ }
+ return NULL;
+ case STORAGE_INVALID:
+ case STORAGE_NEVER:
+ return b;
+ case STORAGE_UINTPTR:
+ if (db->storage == STORAGE_SIZE
+ || db->storage == STORAGE_NULL) {
+ return a;
+ }
+ if (db->storage == STORAGE_POINTER) {
+ return b;
+ }
+ return NULL;
+ // Cannot be promoted
+ case STORAGE_ARRAY:
+ case STORAGE_BOOL:
+ case STORAGE_DONE:
+ case STORAGE_FUNCTION:
+ case STORAGE_NOMEM:
+ case STORAGE_OPAQUE:
+ case STORAGE_RUNE:
+ case STORAGE_SLICE:
+ case STORAGE_STRING:
+ case STORAGE_STRUCT:
+ case STORAGE_TAGGED:
+ case STORAGE_TUPLE:
+ case STORAGE_UNION:
+ case STORAGE_VALIST:
+ case STORAGE_VOID:
+ case STORAGE_UNDEFINED:
+ return NULL;
+ // Handled above
+ case STORAGE_ALIAS:
+ case STORAGE_ERROR:
+ case STORAGE_FCONST:
+ case STORAGE_ICONST:
+ case STORAGE_RCONST:
+ assert(0);
+ }
+ assert(0);
+}
+
+static void resolve_enum_field(struct context *ctx, struct scope_object *obj);
+
+static bool
+type_has_default(struct context *ctx, const struct type *type)
+{
+ switch (type->storage) {
+ case STORAGE_BOOL:
+ case STORAGE_DONE:
+ case STORAGE_INVALID:
+ case STORAGE_F32:
+ case STORAGE_F64:
+ case STORAGE_I16:
+ case STORAGE_I32:
+ case STORAGE_I64:
+ case STORAGE_I8:
+ case STORAGE_INT:
+ case STORAGE_NOMEM:
+ case STORAGE_RUNE:
+ case STORAGE_SIZE:
+ case STORAGE_SLICE:
+ case STORAGE_STRING:
+ case STORAGE_U16:
+ case STORAGE_U32:
+ case STORAGE_U64:
+ case STORAGE_U8:
+ case STORAGE_UINT:
+ case STORAGE_UINTPTR:
+ case STORAGE_VOID:
+ case STORAGE_UNDEFINED:
+ return true;
+ case STORAGE_FUNCTION:
+ case STORAGE_NEVER:
+ case STORAGE_OPAQUE:
+ case STORAGE_TAGGED:
+ case STORAGE_VALIST:
+ return false;
+ case STORAGE_ARRAY:
+ return type->array.length != SIZE_UNDEFINED
+ && type_has_default(ctx, type->array.members);
+ case STORAGE_ENUM:
+ for (struct scope_object *obj = type->_enum.values->objects;
+ obj != NULL; obj = obj->lnext) {
+ if (obj->otype == O_DECL) {
+ continue;
+ }
+ if (obj->otype == O_SCAN) {
+ wrap_resolver(ctx, obj, resolve_enum_field);
+ }
+ assert(obj->otype == O_CONST);
+ if (obj->value->literal.uval == 0) {
+ return true;
+ }
+ }
+ return false;
+ case STORAGE_POINTER:
+ return type->pointer.nullable;
+ case STORAGE_STRUCT:
+ for (struct struct_field *sf = type->struct_union.fields;
+ sf != NULL; sf = sf->next) {
+ if (!type_has_default(ctx, sf->type)) {
+ return false;
+ }
+ }
+ return true;
+ case STORAGE_UNION:
+ for (struct struct_field *sf = type->struct_union.fields;
+ sf != NULL; sf = sf->next) {
+ if (type_has_default(ctx, sf->type)) {
+ return true;
+ }
+ }
+ return false;
+ case STORAGE_TUPLE:
+ for (const struct type_tuple *t = &type->tuple;
+ t != NULL; t = t->next) {
+ if (!type_has_default(ctx, t->type)) {
+ return false;
+ }
+ }
+ return true;
+ case STORAGE_ALIAS:
+ case STORAGE_ERROR:
+ return type_has_default(ctx, type_dealias(ctx, type));
+ case STORAGE_FCONST:
+ case STORAGE_ICONST:
+ case STORAGE_NULL:
+ case STORAGE_RCONST:
+ abort(); // unreachable
+ }
+ abort(); // Unreachable
+}
+
+static void
+check_expr_binarithm(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_BINARITHM;
+ expr->binarithm.op = aexpr->binarithm.op;
+
+ struct expression *lvalue = xcalloc(1, sizeof(struct expression)),
+ *rvalue = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->binarithm.lvalue, lvalue, NULL);
+ check_expression(ctx, aexpr->binarithm.rvalue, rvalue, NULL);
+ if (lvalue->result->storage == STORAGE_INVALID
+ || rvalue->result->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return;
+ }
+
+ expr->result = type_promote(ctx, lvalue->result, rvalue->result);
+ if (expr->result == NULL) {
+ char *ltypename = gen_typename(lvalue->result);
+ char *rtypename = gen_typename(rvalue->result);
+ error(ctx, aexpr->loc, expr, "Cannot promote %s and %s",
+ ltypename, rtypename);
+ free(ltypename);
+ free(rtypename);
+ return;
+ }
+ expr->binarithm.lvalue = lower_implicit_cast(ctx, expr->result, lvalue);
+ expr->binarithm.rvalue = lower_implicit_cast(ctx, expr->result, rvalue);
+
+ check_binarithm_op(ctx, expr, expr->binarithm.op);
+}
+
+static bool
+create_unpack_bindings(struct context *ctx,
+ const struct type *type,
+ const struct location loc,
+ const struct ast_binding_names *names,
+ bool is_static,
+ struct expression_binding *binding)
+{
+ type = type_dealias(ctx, type);
+
+ if (type->storage != STORAGE_TUPLE) {
+ error(ctx, loc, NULL,
+ "Cannot unpack non-tuple type");
+ return false;
+ }
+
+ binding->unpack = xcalloc(1, sizeof(struct binding_unpack));
+ struct binding_unpack *unpack = binding->unpack;
+ const struct type_tuple *type_tuple = &type->tuple;
+
+ while (names != NULL && type_tuple != NULL) {
+ if (type_tuple->type->size == SIZE_UNDEFINED) {
+ error(ctx, loc, NULL,
+ "Cannot create binding of undefined size");
+ return false;
+ }
+ if (names->name != NULL) {
+ if (unpack->object != NULL) {
+ unpack->next = xcalloc(1,
+ sizeof(struct binding_unpack));
+ unpack = unpack->next;
+ }
+ if (is_static) {
+ // Generate a static declaration ident
+ unpack->object = scope_insert(ctx->scope, O_DECL,
+ intern_generated(ctx, "static.%d"),
+ names->name, type_tuple->type, NULL);
+ } else {
+ unpack->object = scope_insert(ctx->scope,
+ O_BIND, names->name, names->name,
+ type_tuple->type, NULL);
+ }
+ unpack->offset = type_tuple->offset;
+ }
+
+ names = names->next;
+ type_tuple = type_tuple->next;
+ }
+
+ if (binding->unpack->object == NULL) {
+ error(ctx, loc, NULL,
+ "Must have at least one non-underscore value when unpacking tuples");
+ return false;
+ }
+ if (type_tuple != NULL) {
+ error(ctx, loc, NULL,
+ "Fewer bindings than tuple elements were provided when unpacking");
+ return false;
+ }
+ if (names != NULL) {
+ error(ctx, loc, NULL,
+ "More bindings than tuple elements were provided when unpacking");
+ return false;
+ }
+
+ return true;
+}
+
+static void
+check_expr_binding(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ assert(aexpr->type == EXPR_BINDING || aexpr->type == EXPR_DEFINE);
+ expr->type = aexpr->type;
+ expr->result = &builtin_type_void;
+
+ struct expression_binding *binding = &expr->binding;
+ const struct ast_expression_binding *abinding = &aexpr->binding;
+ while (abinding) {
+ const struct type *type = NULL;
+ if (abinding->type) {
+ type = type_store_lookup_atype(ctx, abinding->type);
+ }
+
+ struct expression *initializer =
+ xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, abinding->initializer, initializer, type);
+
+ if (abinding->type
+ && abinding->type->storage == STORAGE_ARRAY
+ && abinding->type->array.contextual) {
+ if (initializer->result->storage == STORAGE_INVALID) {
+ // no-op
+ } else if (initializer->result->storage != STORAGE_ARRAY) {
+ error(ctx, aexpr->loc, expr,
+ "Cannot infer array length from non-array type");
+ return;
+ } else if (initializer->result->array.members
+ != type->array.members) {
+ char *inittype = gen_typename(initializer->result);
+ char *bindingtype = gen_typename(type);
+ error(ctx, aexpr->loc, expr,
+ "Initializer of type %s is not assignable to binding type %s",
+ inittype, bindingtype);
+ free(inittype);
+ free(bindingtype);
+ return;
+ }
+ type = initializer->result;
+ }
+
+ if (expr->type == EXPR_DEFINE) {
+ if (type) {
+ initializer = lower_implicit_cast(
+ ctx, type, initializer);
+ }
+ struct expression *value =
+ xcalloc(1, sizeof(struct expression));
+ if (!eval_expr(ctx, initializer, value)) {
+ error(ctx, initializer->loc, value,
+ "Unable to evaluate constant initializer at compile time");
+ type = &builtin_type_invalid;
+ }
+ binding->initializer = value;
+ assert(abinding->names.name != NULL);
+ assert(abinding->names.next == NULL);
+ binding->object = scope_insert(ctx->scope, O_CONST,
+ abinding->names.name, abinding->names.name,
+ NULL, value);
+ goto done;
+ }
+ if (!type) {
+ type = initializer->result;
+ }
+ if (abinding->names.next != NULL) {
+ if (!create_unpack_bindings(ctx, type,
+ abinding->initializer->loc, &abinding->names,
+ abinding->is_static, binding)) {
+ mkerror(expr);
+ }
+ } else if (abinding->names.name != NULL) {
+ if (abinding->is_static) {
+ // Generate a static declaration ident
+ binding->object = scope_insert(ctx->scope, O_DECL,
+ intern_generated(ctx, "static.%d"),
+ abinding->names.name, type, NULL);
+ } else {
+ binding->object = scope_insert(ctx->scope, O_BIND,
+ abinding->names.name, abinding->names.name,
+ type, NULL);
+ }
+ }
+
+ if (type->storage == STORAGE_NULL) {
+ error(ctx, aexpr->loc, expr,
+ "Null is not a valid type for a binding");
+ return;
+ }
+ if (!type_is_assignable(ctx, type, initializer->result)) {
+ char *inittype = gen_typename(initializer->result);
+ char *bindingtype = gen_typename(type);
+ error(ctx, aexpr->loc, expr,
+ "Initializer of type %s is not assignable to binding type %s",
+ inittype, bindingtype);
+ free(inittype);
+ free(bindingtype);
+ return;
+ }
+ type = lower_flexible(ctx, type, NULL);
+ if (type->size == SIZE_UNDEFINED) {
+ error(ctx, aexpr->loc, expr,
+ "Cannot create binding for type of undefined size");
+ return;
+ }
+ binding->initializer = lower_implicit_cast(ctx, type, initializer);
+
+ if (abinding->is_static) {
+ struct expression *value =
+ xcalloc(1, sizeof(struct expression));
+ if (!eval_expr(ctx, binding->initializer, value)) {
+ error(ctx, abinding->initializer->loc, expr,
+ "Unable to evaluate static initializer at compile time");
+ return;
+ }
+ // TODO: Free initializer
+ binding->initializer = value;
+ }
+
+done:
+ if (abinding->next) {
+ binding->next = xcalloc(1,
+ sizeof(struct expression_binding));
+ binding = binding->next;
+ }
+
+ abinding = abinding->next;
+ }
+}
+
+static void
+check_expr_call(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_CALL;
+
+ struct expression *lvalue = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->call.lvalue, lvalue, NULL);
+ expr->call.lvalue = lvalue;
+
+ const struct type *fntype = check_autodereference(ctx,
+ aexpr->loc, lvalue->result);
+ fntype = type_dealias(ctx, fntype);
+ if (fntype->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return;
+ }
+ if (fntype->storage != STORAGE_FUNCTION) {
+ error(ctx, aexpr->loc, expr, "Cannot call non-function type");
+ return;
+ }
+ if (fntype->func.variadism != VARIADISM_HARE && aexpr->call.variadic) {
+ error(ctx, aexpr->loc, NULL,
+ "Function type does not permit variadic argument list");
+ }
+ expr->result = fntype->func.result;
+
+ struct call_argument *arg, **next = &expr->call.args;
+ struct ast_expression_list *aarg = aexpr->call.args;
+ struct type_func_param *param = fntype->func.params;
+ while (param && aarg) {
+ arg = *next = xcalloc(1, sizeof(struct call_argument));
+ arg->value = xcalloc(1, sizeof(struct expression));
+
+ struct ast_expression val;
+ if (!param->next && fntype->func.variadism == VARIADISM_HARE
+ && !aexpr->call.variadic) {
+ // lower the rest to an array
+ val = (struct ast_expression){
+ .loc = aarg->expr->loc,
+ .type = EXPR_LITERAL,
+ .literal = {
+ .storage = STORAGE_ARRAY,
+ .array.exprs = aarg,
+ },
+ };
+ } else {
+ val = *aarg->expr;
+ }
+
+ check_expression(ctx, &val, arg->value, param->type);
+ if (!type_is_assignable(ctx, param->type, arg->value->result)) {
+ char *argtypename = gen_typename(arg->value->result);
+ char *paramtypename = gen_typename(param->type);
+ error(ctx, val.loc, NULL,
+ "Argument type %s is not assignable to parameter type %s",
+ argtypename, paramtypename);
+ free(argtypename);
+ free(paramtypename);
+ return;
+ }
+ arg->value = lower_implicit_cast(ctx, param->type, arg->value);
+ if (!param->next && fntype->func.variadism == VARIADISM_HARE) {
+ return;
+ }
+
+ aarg = aarg->next;
+ next = &arg->next;
+ param = param->next;
+ }
+ while (param && param->default_value) {
+ arg = *next = xcalloc(1, sizeof(struct call_argument));
+ arg->value = param->default_value;
+ next = &arg->next;
+ param = param->next;
+ }
+ if (param) {
+ if (fntype->func.variadism == VARIADISM_HARE && !param->next) {
+ // No variadic arguments, lower to empty slice
+ arg = *next = xcalloc(1, sizeof(struct call_argument));
+ arg->value = xcalloc(1, sizeof(struct expression));
+ *arg->value = (struct expression){
+ .type = EXPR_LITERAL,
+ .result = param->type,
+ .literal = {
+ .object = NULL,
+ .slice.array = NULL,
+ .slice.len = 0,
+ },
+ };
+ return;
+ } else if (param->default_value == NULL) {
+ error(ctx, aexpr->loc, NULL,
+ "Not enough arguments for function call");
+ return;
+ }
+ } else if (aarg) {
+ if (fntype->func.variadism != VARIADISM_C) {
+ error(ctx, aexpr->loc, NULL,
+ "Too many arguments for function call");
+ return;
+ }
+ while (aarg) {
+ arg = *next = xcalloc(1, sizeof(struct call_argument));
+ arg->value = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aarg->expr, arg->value, NULL);
+ aarg = aarg->next;
+ next = &arg->next;
+ }
+ }
+}
+
+static void
+check_expr_cast(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_CAST;
+ expr->cast.kind = aexpr->cast.kind;
+ struct expression *value = expr->cast.value =
+ xcalloc(1, sizeof(struct expression));
+ const struct type *secondary = expr->cast.secondary =
+ type_store_lookup_atype(ctx, aexpr->cast.type);
+ check_expression(ctx, aexpr->cast.value, value, secondary);
+
+ const struct type *primary = type_dealias(ctx, expr->cast.value->result);
+ if (primary->storage == STORAGE_INVALID
+ || secondary->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return;
+ }
+
+ struct match_context mctx = {0};
+ switch (aexpr->cast.kind) {
+ case C_ASSERTION:
+ case C_TEST:
+ if (!begin_check_match(ctx, &mctx, expr,
+ expr->cast.value->result,
+ aexpr->cast.value->loc)) {
+ return;
+ }
+ if (!check_match_case(&mctx, secondary, expr, aexpr->cast.type->loc)) {
+ return;
+ }
+ break;
+ case C_CAST:;
+ const struct type *intermediary =
+ type_is_castable(ctx, secondary, value->result);
+ if (intermediary == NULL) {
+ char *primarytypename = gen_typename(value->result);
+ char *secondarytypename = gen_typename(secondary);
+ error(ctx, aexpr->cast.type->loc, expr,
+ "Invalid cast from %s to %s",
+ primarytypename, secondarytypename);
+ free(primarytypename);
+ free(secondarytypename);
+ return;
+ }
+ // intermediary type is required when casting to tagged union
+ // whose member is an alias of primary type, since gen.c asserts
+ // that the primary type is a direct member of the tagged union.
+ // The value is first cast to an intermediary type which is a
+ // direct member of the tagged union, before being cast to the
+ // tagged union itself.
+ expr->cast.value = lower_implicit_cast(ctx, intermediary, value);
+ break;
+ }
+ expr->result = aexpr->cast.kind == C_TEST? &builtin_type_bool : secondary;
+}
+
+static void
+check_expr_array_literal(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ size_t len = 0;
+ struct ast_expression_list *item = aexpr->literal.array.exprs;
+ struct array_literal *cur, **next = &expr->literal.array;
+ const struct type *type = NULL;
+ if (hint) {
+ hint = type_dealias(ctx, hint);
+
+ size_t narray = 0;
+ switch (hint->storage) {
+ case STORAGE_ARRAY:
+ case STORAGE_SLICE:
+ type = hint->array.members;
+ break;
+ case STORAGE_TAGGED:;
+ const struct type_tagged_union *htagged = &hint->tagged;
+ for (size_t i = 0; i < htagged->len; i++) {
+ const struct type *t =
+ type_dealias(ctx, htagged->types[i]);
+ if (t->storage == STORAGE_ARRAY
+ || t->storage == STORAGE_SLICE) {
+ hint = t;
+ type = hint->array.members;
+ ++narray;
+ }
+ }
+ if (narray != 1) {
+ type = hint = NULL;
+ }
+ break;
+ default:
+ hint = NULL;
+ break;
+ }
+ }
+
+ while (item) {
+ struct expression *value = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, item->expr, value, type);
+ cur = *next = xcalloc(1, sizeof(struct array_literal));
+ cur->value = value;
+
+ if (!type) {
+ type = value->result;
+ } else {
+ if (!type_is_assignable(ctx, type, value->result)) {
+ char *typename1 = gen_typename(type);
+ char *typename2 = gen_typename(value->result);
+ error(ctx, item->expr->loc, expr,
+ "Array members must be of a uniform type, previously seen %s, but now see %s",
+ typename1, typename2);
+ free(typename1);
+ free(typename2);
+ return;
+ }
+ if (!hint) {
+ // The promote_flexible in
+ // type_is_assignable might've caused the
+ // type to change out from under our feet
+ type = expr->literal.array->value->result;
+ }
+ cur->value = lower_implicit_cast(ctx, type, cur->value);
+ }
+
+ item = item->next;
+ next = &cur->next;
+ ++len;
+ }
+
+ if (type == NULL) {
+ error(ctx, aexpr->loc, expr, "Cannot infer array type from context, try casting it to the desired type");
+ return;
+ }
+ expr->result = type_store_lookup_array(ctx, aexpr->loc,
+ type, len, aexpr->literal.array.expand);
+}
+
+static void
+check_expr_compound(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_COMPOUND;
+
+ struct scope *scope = scope_push(&ctx->scope, SCOPE_COMPOUND);
+ scope->hint = hint;
+ expr->compound.scope = scope;
+
+ if (aexpr->compound.label) {
+ expr->compound.label = aexpr->compound.label;
+ scope->label = aexpr->compound.label;
+ }
+
+ struct expressions *list = &expr->compound.exprs;
+ struct expressions **next = &list->next;
+
+ const struct ast_expression_list *alist = &aexpr->compound.list;
+ struct expression *lexpr = NULL;
+ while (alist) {
+ lexpr = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, alist->expr, lexpr, NULL);
+ if (type_has_error(ctx, lexpr->result)) {
+ error(ctx, alist->expr->loc, lexpr,
+ "Cannot ignore error here");
+ }
+ list->expr = lexpr;
+
+ alist = alist->next;
+ if (alist) {
+ *next = xcalloc(1, sizeof(struct expressions));
+ list = *next;
+ next = &list->next;
+ }
+ if (alist && lexpr->result->storage == STORAGE_NEVER) {
+ error(ctx, alist->expr->loc, expr,
+ "Expression with result 'never' may not be followed by additional expressions");
+ }
+ }
+
+ if (lexpr->result->storage != STORAGE_NEVER) {
+ // Add implicit `yield void` if control reaches end of compound
+ // expression.
+ tagged_append(&scope->results, &builtin_type_void);
+
+ list->next = xcalloc(1, sizeof(struct expressions));
+ struct ast_expression *yexpr = xcalloc(1, sizeof(struct ast_expression));
+ yexpr->type = EXPR_YIELD;
+ lexpr = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, yexpr, lexpr, NULL);
+ list->next->expr = lexpr;
+ }
+ expr->result = type_store_reduce_result(ctx, aexpr->loc,
+ &scope->results);
+
+ for (struct yield *yield = scope->yields; yield;) {
+ *yield->expression = lower_implicit_cast(ctx, expr->result,
+ *yield->expression);
+
+ struct yield *next = yield->next;
+ free(yield);
+ yield = next;
+ }
+
+ assert(expr->result);
+ scope_pop(&ctx->scope);
+}
+
+static void
+check_expr_literal(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_LITERAL;
+ enum type_storage storage = aexpr->literal.storage;
+ expr->result = builtin_type_for_storage(storage);
+
+ switch (aexpr->literal.storage) {
+ case STORAGE_ICONST:
+ expr->result = type_create_flexible(storage,
+ aexpr->literal.ival, aexpr->literal.ival);
+ /* fallthrough */
+ case STORAGE_I8:
+ case STORAGE_I16:
+ case STORAGE_I32:
+ case STORAGE_I64:
+ case STORAGE_INT:
+ expr->literal.ival = aexpr->literal.ival;
+ break;
+ case STORAGE_U8:
+ case STORAGE_U16:
+ case STORAGE_U32:
+ case STORAGE_U64:
+ case STORAGE_UINT:
+ case STORAGE_SIZE:
+ expr->literal.uval = aexpr->literal.uval;
+ break;
+ case STORAGE_RCONST:
+ expr->result = type_create_flexible(storage,
+ aexpr->literal.rune, aexpr->literal.rune);
+ expr->literal.rune = aexpr->literal.rune;
+ break;
+ case STORAGE_BOOL:
+ expr->literal.bval = aexpr->literal.bval;
+ break;
+ case STORAGE_DONE:
+ case STORAGE_NOMEM:
+ case STORAGE_NULL:
+ case STORAGE_VOID:
+ // No storage
+ break;
+ case STORAGE_ARRAY:
+ check_expr_array_literal(ctx, aexpr, expr, hint);
+ break;
+ case STORAGE_STRING:
+ expr->literal.string.len = aexpr->literal.string.len;
+ expr->literal.string.value = xcalloc(1, aexpr->literal.string.len);
+ memcpy(expr->literal.string.value, aexpr->literal.string.value,
+ aexpr->literal.string.len);
+ break;
+ case STORAGE_FCONST:
+ expr->result = type_create_flexible(storage,
+ aexpr->literal.fval, aexpr->literal.fval);
+ // fallthrough
+ case STORAGE_F32:
+ case STORAGE_F64:
+ expr->literal.fval = aexpr->literal.fval;
+ break;
+ case STORAGE_ENUM:
+ case STORAGE_INVALID:
+ case STORAGE_UINTPTR:
+ case STORAGE_ALIAS:
+ case STORAGE_ERROR:
+ case STORAGE_FUNCTION:
+ case STORAGE_NEVER:
+ case STORAGE_OPAQUE:
+ case STORAGE_POINTER:
+ case STORAGE_RUNE:
+ case STORAGE_SLICE:
+ case STORAGE_TAGGED:
+ case STORAGE_TUPLE:
+ case STORAGE_STRUCT:
+ case STORAGE_UNION:
+ case STORAGE_UNDEFINED:
+ case STORAGE_VALIST:
+ assert(0); // Invariant
+ }
+}
+
+static void
+check_expr_defer(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_DEFER;
+ expr->result = &builtin_type_void;
+ expr->defer.deferred = xcalloc(1, sizeof(struct expression));
+ expr->defer.scope = scope_push(&ctx->scope, SCOPE_DEFER);
+ check_expression(ctx, aexpr->defer.deferred, expr->defer.deferred, NULL);
+ if (type_has_error(ctx, expr->defer.deferred->result)) {
+ error(ctx, aexpr->defer.deferred->loc, expr->defer.deferred,
+ "Cannot ignore error here");
+ }
+ scope_pop(&ctx->scope);
+}
+
+static void
+check_expr_delete(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_DELETE;
+ expr->delete.is_static = aexpr->delete.is_static;
+ expr->result = &builtin_type_void;
+ struct expression *dexpr = expr->delete.expr =
+ xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->delete.expr, expr->delete.expr, NULL);
+ const struct type *otype = NULL;
+ switch (dexpr->type) {
+ case EXPR_SLICE:
+ otype = dexpr->slice.object->result;
+ break;
+ case EXPR_ACCESS:
+ if (dexpr->access.type != ACCESS_INDEX) {
+ error(ctx, aexpr->delete.expr->loc, expr,
+ "Deleted expression must be slicing or indexing expression");
+ return;
+ }
+ struct expression *array = dexpr->access.array;
+ if (array->type == EXPR_ACCESS
+ && array->access.type == ACCESS_IDENTIFIER
+ && array->access.object->flags &
+ SO_FOR_EACH_SUBJECT) {
+ error(ctx, aexpr->delete.expr->loc, expr,
+ "cannot delete the subject of for-each loop");
+ }
+ otype = dexpr->access.array->result;
+ break;
+ default:
+ if (dexpr->result->storage != STORAGE_INVALID) {
+ error(ctx, aexpr->delete.expr->loc, expr,
+ "Deleted expression must be slicing or indexing expression");
+ }
+ return;
+ }
+ otype = check_autodereference(ctx, aexpr->loc, otype);
+ otype = type_dealias(ctx, otype);
+ if (otype->storage != STORAGE_SLICE) {
+ error(ctx, aexpr->delete.expr->loc, expr,
+ "delete must operate on a slice");
+ return;
+ }
+}
+
+static void
+check_expr_control(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = aexpr->type;
+ expr->result = &builtin_type_never;
+ expr->control.label = aexpr->control.label;
+
+ enum scope_class want;
+ switch (expr->type) {
+ case EXPR_BREAK:
+ case EXPR_CONTINUE:
+ want = SCOPE_LOOP;
+ break;
+ case EXPR_YIELD:
+ want = SCOPE_COMPOUND;
+ break;
+ default:
+ abort(); // Invariant
+ }
+
+ struct scope *scope = NULL;
+ if (aexpr->control.label) {
+ scope = scope_lookup_label(ctx->scope, aexpr->control.label);
+ if (scope && scope->class != want) {
+ error(ctx, aexpr->loc, NULL,
+ "Selected expression must%s be a loop",
+ want == SCOPE_COMPOUND ? " not" : "");
+ }
+ } else {
+ scope = scope_lookup_class(ctx->scope, want);
+ }
+ if (scope) {
+ struct scope *defer_scope =
+ scope_lookup_class(ctx->scope, SCOPE_DEFER);
+ if (defer_scope) {
+ defer_scope = aexpr->control.label
+ ? scope_lookup_label(defer_scope, aexpr->control.label)
+ : scope_lookup_class(defer_scope, want);
+ if (scope == defer_scope) {
+ error(ctx, aexpr->loc, NULL,
+ "Cannot jump out of defer expression");
+ // continue checking so other errors can be reported
+ }
+ }
+ } else {
+ const char *msg;
+ switch (expr->type) {
+ case EXPR_BREAK:
+ msg = "No eligible loop to break from";
+ break;
+ case EXPR_CONTINUE:
+ msg = "No eligible loop to continue to";
+ break;
+ case EXPR_YIELD:
+ msg = "No eligible expression to yield from";
+ break;
+ default:
+ assert(0); // Invariant
+ }
+ error(ctx, aexpr->loc, NULL, "%s", msg);
+ // No need to continue checking, because we won't have the right
+ // hint for the value without a scope.
+ return;
+ }
+ expr->control.scope = scope;
+
+ if (expr->type == EXPR_CONTINUE) {
+ return;
+ }
+
+ expr->control.value = xcalloc(1, sizeof(struct expression));
+ if (aexpr->control.value) {
+ check_expression(ctx, aexpr->control.value,
+ expr->control.value, scope->hint);
+ } else {
+ expr->control.value->type = EXPR_LITERAL;
+ expr->control.value->result = &builtin_type_void;
+ }
+
+ tagged_append(&scope->results, expr->control.value->result);
+
+ struct yield *yield = xcalloc(1, sizeof(struct yield));
+ yield->expression = &expr->control.value;
+ yield->next = scope->yields;
+ scope->yields = yield;
+}
+
+static void
+check_expr_for_accumulator(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr)
+{
+ struct expression *bindings = NULL, *cond = NULL, *afterthought = NULL;
+
+ if (aexpr->_for.bindings) {
+ bindings = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->_for.bindings, bindings, NULL);
+ if (bindings->result->storage == STORAGE_INVALID) {
+ // It won't be fruitful to continue checking if the
+ // bindings fail.
+ return;
+ }
+ assert(bindings->type == EXPR_BINDING);
+ expr->_for.bindings = bindings;
+ }
+
+ cond = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->_for.cond, cond, &builtin_type_bool);
+ expr->_for.cond = cond;
+ if (type_dealias(ctx, cond->result)->storage != STORAGE_BOOL
+ && cond->result->storage != STORAGE_INVALID) {
+ error(ctx, aexpr->_for.cond->loc, expr,
+ "Expected for condition to be boolean");
+ }
+
+ if (aexpr->_for.afterthought) {
+ afterthought = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->_for.afterthought, afterthought,
+ NULL);
+ if (type_has_error(ctx, afterthought->result)) {
+ error(ctx, aexpr->_for.afterthought->loc, afterthought,
+ "Cannot ignore error here");
+ }
+ expr->_for.afterthought = afterthought;
+ }
+
+ struct expression *body = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->_for.body, body, NULL);
+ if (type_has_error(ctx, body->result)) {
+ error(ctx, aexpr->_for.body->loc, body,
+ "Cannot ignore error here");
+ }
+ expr->_for.body = body;
+
+ struct expression evaled;
+ if (eval_expr(ctx, expr->_for.cond, &evaled) && evaled.literal.bval) {
+ expr->result = &builtin_type_never;
+ }
+}
+
+static void
+check_expr_for_each(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr)
+{
+ struct expression *binding = xcalloc(1, sizeof(struct expression));
+ struct expression *initializer = xcalloc(1, sizeof(struct expression));
+
+ expr->_for.bindings = binding;
+ binding->type = EXPR_BINDING;
+ binding->result = &builtin_type_void;
+ binding->binding.initializer = initializer;
+
+ struct ast_expression_binding *abinding = &aexpr->_for.bindings->binding;
+
+ const struct type *binding_type = NULL, *init_type_hint = NULL;
+
+ if (abinding->type != NULL) {
+ binding_type = type_store_lookup_atype(ctx, abinding->type);
+
+ // Construct a type hint for the init expression. For example,
+ // if the type hint is *int and we are in a &.., we would have
+ // to do: *int -> int -> [_]int
+ init_type_hint = binding_type;
+
+ switch (expr->_for.kind) {
+ case FOR_EACH_POINTER:
+ init_type_hint = type_dealias(ctx, init_type_hint);
+ if (init_type_hint->storage != STORAGE_POINTER) {
+ error(ctx, aexpr->loc, expr,
+ "Expected pointer type");
+ return;
+ }
+ init_type_hint = init_type_hint->pointer.referent;
+ // fallthrough
+ case FOR_EACH_VALUE:
+ init_type_hint = type_store_lookup_array(ctx, aexpr->loc,
+ init_type_hint, SIZE_UNDEFINED, false);
+ break;
+ case FOR_EACH_ITERATOR: {
+ struct type_tagged_union tags = { .types = NULL };
+ if (init_type_hint->storage == STORAGE_TAGGED) {
+ tags = tagged_dup_tags(&init_type_hint->tagged);
+ } else {
+ tagged_append(&tags, binding_type);
+ }
+ tagged_append(&tags, &builtin_type_done);
+ init_type_hint = type_store_lookup_tagged(ctx,
+ aexpr->loc, &tags);
+ break;
+ }
+ default:
+ abort(); // unreachable
+ }
+ }
+ check_expression(ctx, abinding->initializer, initializer, init_type_hint);
+
+ const struct type *initializer_type = type_dealias(ctx,
+ initializer->result);
+ const struct type *var_type = binding_type;
+ const struct type *initializer_result;
+
+ switch (expr->_for.kind) {
+ case FOR_EACH_POINTER:
+ if (abinding->names.next != NULL) {
+ error(ctx, abinding->initializer->loc, expr,
+ "Cannot unpack tuple by pointer in for-each loop");
+ return;
+ }
+ // fallthrough
+ case FOR_EACH_VALUE:
+ initializer_type = type_dealias(ctx, check_autodereference(ctx,
+ abinding->initializer->loc, initializer_type));
+
+ if (initializer_type->storage != STORAGE_ARRAY
+ && initializer_type->storage != STORAGE_SLICE) {
+ error(ctx, abinding->initializer->loc, initializer,
+ "Expected array or slice");
+ return;
+ }
+ if (initializer_type->storage == STORAGE_ARRAY
+ && initializer_type->size == SIZE_UNDEFINED) {
+ error(ctx, abinding->initializer->loc, initializer,
+ "Cannot iterate over array of type with undefined size");
+ return;
+ }
+ if (expr->_for.kind == FOR_EACH_VALUE) {
+ initializer_result = initializer_type->array.members;
+ } else {
+ initializer_result = type_store_lookup_pointer(ctx,
+ aexpr->loc, initializer_type->array.members, false);
+ }
+ break;
+ case FOR_EACH_ITERATOR:
+ if (initializer_type->storage == STORAGE_INVALID) {
+ initializer_result = &builtin_type_invalid;
+ break;
+ } else if (initializer_type->storage != STORAGE_TAGGED) {
+ error(ctx, abinding->initializer->loc, initializer,
+ "Expected tagged union");
+ return;
+ }
+
+ // Remove all done tags and aliases of it from the tagged union
+ struct type_tagged_union tags =
+ tagged_dup_tags(&initializer_type->tagged);
+ int done_tags_found = 0;
+ size_t new_len = 0;
+ for (size_t i = 0; i < tags.len; i++) {
+ if (type_dealias(ctx, tags.types[i])->storage == STORAGE_DONE) {
+ done_tags_found++;
+ continue;
+ }
+ tags.types[new_len++] = tags.types[i];
+ }
+ tags.len = new_len;
+ if (done_tags_found != 1) {
+ error(ctx, abinding->initializer->loc, initializer,
+ "Tagged union must contain exactly one done type");
+ return;
+ }
+ initializer_result = type_store_reduce_result(ctx,
+ abinding->initializer->loc, &tags);
+ break;
+ default:
+ abort();
+ }
+
+ if (var_type == NULL) {
+ var_type = initializer_result;
+ }
+ if (var_type->size == SIZE_UNDEFINED) {
+ error(ctx, abinding->initializer->loc, binding,
+ "Cannot create binding of undefined size");
+ // error is recoverable
+ }
+ if (abinding->names.next != NULL) {
+ if (!create_unpack_bindings(ctx, var_type, initializer->loc,
+ &abinding->names, abinding->is_static, &binding->binding)) {
+ mkerror(binding);
+
+ };
+ } else if (abinding->names.name != NULL) {
+ binding->binding.object = scope_insert(ctx->scope, O_BIND,
+ abinding->names.name, abinding->names.name, var_type, NULL);
+ }
+
+ if (binding_type != NULL && !type_is_assignable(ctx, var_type, initializer_result)) {
+ char *init = gen_typename(initializer_result);
+ char *bind = gen_typename(var_type);
+ error(ctx, aexpr->loc, expr,
+ "Initializer of type %s is not assignable to binding of type %s",
+ init, bind);
+ free(init);
+ free(bind);
+ return;
+ }
+
+ struct expression *body = xcalloc(1, sizeof(struct expression));
+ expr->_for.body = body;
+
+ if (expr->_for.kind != FOR_EACH_ITERATOR
+ && initializer->type == EXPR_ACCESS
+ && initializer->access.type == ACCESS_IDENTIFIER) {
+ initializer->access.object->flags
+ |= SO_FOR_EACH_SUBJECT;
+
+ check_expression(ctx, aexpr->_for.body, body, NULL);
+
+ initializer->access.object->flags
+ &= ~(SO_FOR_EACH_SUBJECT);
+ } else {
+ check_expression(ctx, aexpr->_for.body, body, NULL);
+ }
+
+ if (type_has_error(ctx, body->result)) {
+ error(ctx, aexpr->_for.body->loc, body,
+ "Cannot ignore error here");
+ }
+}
+
+static void
+check_expr_for(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_FOR;
+ expr->result = &builtin_type_void;
+ expr->_for.kind = aexpr->_for.kind;
+
+ struct scope *scope = scope_push(&ctx->scope, SCOPE_LOOP);
+ scope->hint = hint;
+ expr->_for.scope = scope;
+
+ if (aexpr->_for.label) {
+ expr->_for.label = aexpr->_for.label;
+ scope->label = aexpr->_for.label;
+ }
+
+ switch (expr->_for.kind) {
+ case FOR_ACCUMULATOR:
+ check_expr_for_accumulator(ctx, aexpr, expr);
+ break;
+ case FOR_EACH_VALUE:
+ case FOR_EACH_POINTER:
+ case FOR_EACH_ITERATOR:
+ check_expr_for_each(ctx, aexpr, expr);
+ break;
+ }
+
+ scope_pop(&ctx->scope);
+
+ // The else branch is not evaluated in the loop scope.
+ expr->_for.else_branch = xcalloc(1, sizeof(struct expression));
+ if (aexpr->_for.else_branch) {
+ check_expression(ctx, aexpr->_for.else_branch,
+ expr->_for.else_branch, hint);
+ } else {
+ expr->_for.else_branch->type = EXPR_LITERAL;
+ expr->_for.else_branch->result = &builtin_type_void;
+ }
+ // Check this later, because we should unconditionally typecheck the
+ // else branch
+ if (expr->result != &builtin_type_never) {
+ expr->result = expr->_for.else_branch->result;
+ } else {
+ expr->_for.else_branch = NULL;
+ };
+
+ // If every possible result type is assignable to the hint, just set the
+ // hint as the result type.
+ bool assignable_to_hint = true;
+ if (hint && type_is_assignable(ctx, hint, expr->result)) {
+ for (size_t i = 0; i < scope->results.len; i++) {
+ if (!type_is_assignable(ctx, hint, scope->results.types[i])) {
+ assignable_to_hint = false;
+ break;
+ }
+ }
+ } else {
+ assignable_to_hint = false;
+ }
+ if (assignable_to_hint) {
+ // If we were going to end up with `never` as our result, keep
+ // it regardless of the hint
+ if (scope->results.len != 0 || expr->_for.else_branch) {
+ expr->result = hint;
+ }
+ } else {
+ tagged_append(&scope->results, expr->result);
+ expr->result = type_store_reduce_result(ctx,
+ aexpr->loc, &scope->results);
+ }
+
+ // Lower the break values to the result type.
+ for (struct yield *yield = scope->yields; yield;) {
+ *yield->expression = lower_implicit_cast(ctx, expr->result,
+ *yield->expression);
+
+ struct yield *next = yield->next;
+ free(yield);
+ yield = next;
+ }
+ if (expr->_for.else_branch) {
+ expr->_for.else_branch =
+ lower_implicit_cast(ctx, expr->result, expr->_for.else_branch);
+ }
+}
+
+static void
+check_expr_free(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ assert(aexpr->type == EXPR_FREE);
+ expr->type = EXPR_FREE;
+ expr->free.expr = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->free.expr, expr->free.expr, NULL);
+
+ if (expr->free.expr->type == EXPR_ACCESS
+ && expr->free.expr->access.type == ACCESS_IDENTIFIER
+ && expr->free.expr->access.object->flags
+ & SO_FOR_EACH_SUBJECT) {
+ error(ctx, aexpr->free.expr->loc, expr,
+ "cannot free the subject of for-each loop");
+ }
+
+ enum type_storage storage = type_dealias(ctx, expr->free.expr->result)->storage;
+ if (storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return;
+ }
+ if (storage != STORAGE_SLICE
+ && storage != STORAGE_STRING
+ && storage != STORAGE_POINTER
+ && storage != STORAGE_NULL) {
+ error(ctx, aexpr->free.expr->loc, expr,
+ "free must operate on slice, string, pointer, or null");
+ return;
+ }
+ expr->result = &builtin_type_void;
+}
+
+static void
+check_expr_if(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_IF;
+
+ struct expression *cond, *true_branch, *false_branch;
+
+ cond = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->_if.cond, cond, &builtin_type_bool);
+
+ true_branch = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->_if.true_branch, true_branch, hint);
+ false_branch = xcalloc(1, sizeof(struct expression));
+ if (aexpr->_if.false_branch) {
+ check_expression(ctx, aexpr->_if.false_branch, false_branch, hint);
+ } else {
+ false_branch->type = EXPR_LITERAL;
+ false_branch->result = &builtin_type_void;
+ }
+ const struct type *fresult = false_branch->result;
+ if (hint && type_is_assignable(ctx, hint, true_branch->result)
+ && type_is_assignable(ctx, hint, fresult)) {
+ expr->result = hint;
+ } else {
+ const struct type *tags[] = { fresult, true_branch->result };
+ struct type_tagged_union tagged = { .types = tags, .len = 2 };
+ expr->result = type_store_reduce_result(ctx, aexpr->loc, &tagged);
+ }
+ true_branch = lower_implicit_cast(ctx, expr->result, true_branch);
+ false_branch = lower_implicit_cast(ctx, expr->result, false_branch);
+
+ if (cond->result->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return;
+ }
+ if (type_dealias(ctx, cond->result)->storage != STORAGE_BOOL) {
+ error(ctx, aexpr->_if.cond->loc, expr,
+ "Expected if condition to be boolean");
+ return;
+ }
+
+ expr->_if.cond = cond;
+ expr->_if.true_branch = true_branch;
+ expr->_if.false_branch = false_branch;
+}
+
+static void
+check_expr_match(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_MATCH;
+
+ struct expression *value = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->match.value, value, NULL);
+ expr->match.value = value;
+
+ struct match_context mctx = {0};
+ if (!begin_check_match(ctx, &mctx, expr, value->result, aexpr->match.value->loc)) {
+ return;
+ }
+
+ struct type_tagged_union result_type = { .types = NULL };
+
+ struct match_case **next = &expr->match.cases, *_case = NULL;
+ for (struct ast_match_case *acase = aexpr->match.cases;
+ acase; acase = acase->next) {
+ _case = *next = xcalloc(1, sizeof(struct match_case));
+ next = &_case->next;
+
+ const struct type *ctype = NULL;
+ if (acase->type) {
+ ctype = type_store_lookup_atype(ctx, acase->type);
+ if (!check_match_case(&mctx, ctype, expr, acase->type->loc)) {
+ return;
+ }
+ if (ctype->size == SIZE_UNDEFINED) {
+ error(ctx, acase->type->loc, expr,
+ "Can't use type of undefined size in match case");
+ return;
+ }
+ }
+
+ if (acase->name != NULL) {
+ assert(ctype);
+ if (ctype->storage == STORAGE_NULL) {
+ error(ctx, aexpr->loc, expr,
+ "Null is not a valid type for a binding");
+ return;
+ }
+ struct scope *scope = scope_push(&ctx->scope, SCOPE_MATCH);
+ _case->object = scope_insert(scope, O_BIND, acase->name,
+ acase->name, ctype, NULL);
+ }
+
+ _case->value = xcalloc(1, sizeof(struct expression));
+ _case->type = ctype;
+
+ // Lower to compound
+ // TODO: This should probably be done in a more first-class way
+ struct ast_expression compound = {
+ .type = EXPR_COMPOUND,
+ .loc = acase->exprs.expr->loc,
+ .compound = {
+ .label = aexpr->match.label,
+ .list = acase->exprs,
+ },
+ };
+ check_expression(ctx, &compound, _case->value, hint);
+
+ if (acase->name != NULL) {
+ scope_pop(&ctx->scope);
+ }
+
+ if (expr->result == NULL) {
+ expr->result = _case->value->result;
+ tagged_append(&result_type, _case->value->result);
+ } else if (expr->result != _case->value->result) {
+ tagged_append(&result_type, _case->value->result);
+ }
+ }
+
+ if (result_type.len > 1) {
+ if (hint) {
+ expr->result = hint;
+ } else {
+ expr->result = type_store_reduce_result(
+ ctx, aexpr->loc, &result_type);
+ }
+
+ struct match_case *_case = expr->match.cases;
+ struct ast_match_case *acase = aexpr->match.cases;
+ while (_case) {
+ if (hint && !type_is_assignable(ctx, hint, _case->value->result)) {
+ error(ctx, acase->exprs.expr->loc, expr,
+ "Match case is not assignable to result type");
+ return;
+ }
+ _case->value = lower_implicit_cast(ctx,
+ expr->result, _case->value);
+ _case = _case->next;
+ acase = acase->next;
+ }
+
+ free(result_type.types);
+ }
+}
+
+static void
+check_expr_measure(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->result = &builtin_type_size;
+ switch (aexpr->measure.op) {
+ case M_ALIGN:
+ case M_SIZE:
+ break;
+ case M_LEN:
+ expr->len.value = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->measure.value, expr->len.value, NULL);
+ const struct type *type = check_autodereference(ctx,
+ aexpr->measure.value->loc, expr->len.value->result);
+ type = type_dealias(ctx, type);
+ enum type_storage vstor = type->storage;
+ bool valid = vstor == STORAGE_ARRAY || vstor == STORAGE_SLICE
+ || vstor == STORAGE_STRING || vstor == STORAGE_INVALID;
+ if (!valid) {
+ char *typename = gen_typename(expr->len.value->result);
+ error(ctx, aexpr->measure.value->loc, expr,
+ "len argument must be of an array, slice, or str type, but got %s",
+ typename);
+ free(typename);
+ return;
+ }
+ if (vstor == STORAGE_ARRAY) {
+ if (type->array.length == SIZE_UNDEFINED) {
+ error(ctx, aexpr->measure.value->loc, expr,
+ "Cannot take length of unbounded array type");
+ return;
+ }
+ expr->type = EXPR_LITERAL;
+ expr->result = &builtin_type_size;
+ expr->literal.object = NULL;
+ expr->literal.uval = type->array.length;
+ return;
+ }
+ expr->type = EXPR_LEN;
+ return;
+ case M_OFFSET:
+ expr->type = EXPR_LITERAL;
+ if (aexpr->measure.value->type != EXPR_ACCESS) {
+ error(ctx, aexpr->measure.value->loc, expr,
+ "offset argument must be a field or tuple access");
+ return;
+ }
+ if (aexpr->measure.value->access.type != ACCESS_FIELD
+ && aexpr->measure.value->access.type != ACCESS_TUPLE) {
+ error(ctx, aexpr->measure.value->loc, expr,
+ "offset argument must be a field or tuple access");
+ return;
+ }
+ struct expression *value = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->measure.value, value, NULL);
+ if (value->result->storage == STORAGE_INVALID) {
+ return;
+ }
+ if (value->access.type == ACCESS_FIELD) {
+ expr->literal.uval = value->access.field->offset;
+ } else {
+ assert(value->access.type == ACCESS_TUPLE);
+ expr->literal.uval = value->access.tvalue->offset;
+ }
+ return;
+ }
+
+ expr->type = EXPR_LITERAL;
+ struct errors **cur_err = ctx->next;
+ struct dimensions dim = type_store_lookup_dimensions(
+ ctx, aexpr->measure.type);
+ if (ctx->next != cur_err) {
+ mkerror(expr);
+ return;
+ }
+ struct ast_types *next = ctx->unresolved;
+ ctx->unresolved = xcalloc(1, sizeof(struct ast_types));
+ ctx->unresolved->type = aexpr->measure.type;
+ ctx->unresolved->next = next;
+ if (aexpr->measure.op == M_ALIGN) {
+ if (dim.align == ALIGN_UNDEFINED) {
+ error(ctx, aexpr->measure.type->loc, expr,
+ "Cannot take alignment of a type with undefined alignment");
+ return;
+ }
+ expr->literal.uval = dim.align;
+ } else {
+ if (dim.size == SIZE_UNDEFINED) {
+ error(ctx, aexpr->measure.type->loc, expr,
+ "Cannot take size of a type with undefined size");
+ return;
+ }
+ expr->literal.uval = dim.size;
+ }
+}
+
+static void
+check_expr_propagate(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ struct expression *lvalue = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->propagate.value, lvalue, hint == &builtin_type_void ? NULL : hint);
+
+ const struct type *intype = lvalue->result;
+ if (intype->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return;
+ }
+ if (type_dealias(ctx, intype)->storage != STORAGE_TAGGED) {
+ char *typename = gen_typename(intype);
+ error(ctx, aexpr->loc, expr,
+ "Cannot use error %s on non-tagged type %s",
+ aexpr->propagate.abort ? "assertion" : "propagation",
+ typename);
+ free(typename);
+ return;
+ }
+ if (!aexpr->propagate.abort) {
+ struct scope *defer = scope_lookup_class(ctx->scope, SCOPE_DEFER);
+ if (defer) {
+ error(ctx, aexpr->loc, expr,
+ "Cannot use error propagation in a defer expression");
+ return;
+ }
+ }
+
+ struct type_tagged_union res = { .types = NULL };
+ struct type_tagged_union ret = { .types = NULL };
+
+ const struct type_tagged_union *intu = &type_dealias(ctx, intype)->tagged;
+ for (size_t i = 0; i < intu->len; i++) {
+ tagged_append(type_is_error(ctx, intu->types[i]) ? &ret : &res,
+ intu->types[i]);
+ }
+
+ if (ret.len == 0) {
+ error(ctx, aexpr->loc, expr,
+ "No error can occur here, cannot %s",
+ aexpr->propagate.abort ? "use error assertion" : "propagate");
+ return;
+ }
+
+ const struct type *return_type =
+ type_store_lookup_tagged(ctx, aexpr->loc, &ret);
+ const struct type *result_type =
+ type_store_lookup_tagged(ctx, aexpr->loc, &res);
+
+ // Lower to a match expression
+ expr->type = EXPR_MATCH;
+ expr->match.value = lvalue;
+
+ struct scope *scope = scope_push(&ctx->scope, SCOPE_MATCH);
+ struct match_case *case_ok = xcalloc(1, sizeof(struct match_case));
+ struct match_case *case_err = xcalloc(1, sizeof(struct match_case));
+
+ struct scope_object *ok_obj = NULL, *err_obj = NULL;
+ if (result_type->size != SIZE_UNDEFINED) {
+ struct ident *id = intern_generated(ctx, "ok.%d");
+ ok_obj = scope_insert(scope, O_BIND, id, id, result_type, NULL);
+ }
+
+ case_ok->type = result_type;
+ case_ok->object = ok_obj;
+ case_ok->value = xcalloc(1, sizeof(struct expression));
+ case_ok->value->result = result_type;
+ case_ok->value->loc = expr->loc;
+ if (ok_obj) {
+ case_ok->value->type = EXPR_ACCESS;
+ case_ok->value->access.type = ACCESS_IDENTIFIER;
+ case_ok->value->access.object = ok_obj;
+ } else {
+ case_ok->value->type = EXPR_LITERAL;
+ }
+
+ case_err->value = xcalloc(1, sizeof(struct expression));
+ case_err->value->loc = expr->loc;
+
+ if (aexpr->propagate.abort) {
+ case_err->value->type = EXPR_ASSERT;
+ case_err->value->assert = (struct expression_assert){
+ .cond = NULL,
+ .message = NULL,
+ .fixed_reason = ABORT_PROPAGATE_ERROR_OCCURRED,
+ };
+ } else {
+ if (return_type->size != SIZE_UNDEFINED) {
+ struct ident *id = intern_generated(ctx, "err.%d");
+ err_obj = scope_insert(scope, O_BIND, id, id, return_type, NULL);
+ }
+ case_err->type = return_type;
+ case_err->object = err_obj;
+ if (!type_is_assignable(ctx, ctx->fntype->func.result, return_type)) {
+ char *res = gen_typename(ctx->fntype->func.result);
+ char *ret = gen_typename(return_type);
+ error(ctx, aexpr->loc, expr,
+ "Error type %s is not assignable to function result type %s",
+ ret, res);
+ free(res);
+ free(ret);
+ return;
+ }
+
+ case_err->value->type = EXPR_RETURN;
+
+ struct expression *rval =
+ xcalloc(1, sizeof(struct expression));
+ rval->result = return_type;
+ rval->loc = expr->loc;
+ if (err_obj != NULL) {
+ rval->type = EXPR_ACCESS;
+ rval->access.type = ACCESS_IDENTIFIER;
+ rval->access.object = err_obj;
+ } else {
+ rval->type = EXPR_LITERAL;
+ }
+ case_err->value->_return.value = lower_implicit_cast(ctx,
+ ctx->fntype->func.result, rval);
+ }
+ case_err->value->result = &builtin_type_never;
+
+ expr->match.cases = case_ok;
+ case_ok->next = case_err;
+
+ scope_pop(&ctx->scope);
+ expr->result = result_type;
+}
+
+static void
+check_expr_return(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ struct scope *defer = scope_lookup_class(ctx->scope, SCOPE_DEFER);
+ if (defer) {
+ error(ctx, aexpr->loc, NULL,
+ "Cannot return inside a defer expression");
+ // continue checking so other errors can be reported
+ }
+
+ expr->type = EXPR_RETURN;
+ expr->result = &builtin_type_never;
+
+ struct expression *rval = expr->_return.value =
+ xcalloc(1, sizeof(struct expression));
+ if (aexpr->control.value) {
+ const struct type *hint = NULL;
+ if (ctx->fntype) {
+ hint = ctx->fntype->func.result;
+ }
+ check_expression(ctx, aexpr->control.value, rval, hint);
+ } else {
+ rval->type = EXPR_LITERAL;
+ rval->result = &builtin_type_void;
+ }
+ if (ctx->fntype == NULL) {
+ error(ctx, aexpr->loc, NULL, "Cannot return outside a function body");
+ return;
+ }
+
+ if (!type_is_assignable(ctx, ctx->fntype->func.result, rval->result)) {
+ char *rettypename = gen_typename(rval->result);
+ char *fntypename = gen_typename(ctx->fntype->func.result);
+ error(ctx, aexpr->loc, NULL,
+ "Return type %s is not assignable to function result type %s",
+ rettypename, fntypename);
+ free(rettypename);
+ free(fntypename);
+ return;
+ }
+ expr->_return.value = lower_implicit_cast(ctx, ctx->fntype->func.result, rval);
+}
+
+static void
+slice_bounds_check(struct context *ctx, struct expression *expr)
+{
+ const struct type *atype = type_dereference(ctx, expr->slice.object->result, false);
+ const struct type *dtype = type_dealias(ctx, atype);
+ struct expression start, end;
+ enum {
+ START = 1, END = 1 << 1, LENGTH = 1 << 2
+ } bounds = 0;
+
+ if (expr->slice.start && eval_expr(ctx, expr->slice.start, &start)) {
+ bounds |= START;
+ }
+ if (expr->slice.end && eval_expr(ctx, expr->slice.end, &end)) {
+ bounds |= END;
+ }
+ if (dtype->storage == STORAGE_ARRAY && dtype->array.length != SIZE_UNDEFINED) {
+ bounds |= LENGTH;
+ }
+
+ if ((bounds & (START | LENGTH)) == (START | LENGTH)
+ && start.literal.uval > dtype->array.length) {
+ error(ctx, expr->loc, expr,
+ "Start index must not be greater than array length");
+ }
+ if ((bounds & (START | END)) == (START | END)
+ && start.literal.uval > end.literal.uval) {
+ error(ctx, expr->loc, expr,
+ "Start index must not be greater than end index");
+ }
+ if ((bounds & (END | LENGTH)) == (END | LENGTH)
+ && end.literal.uval > dtype->array.length) {
+ error(ctx, expr->loc, expr,
+ "End index must not be greater than array length");
+ }
+}
+
+static void
+check_expr_slice(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_SLICE;
+
+ expr->slice.object = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->slice.object, expr->slice.object, NULL);
+ if (expr->slice.object->result->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return;
+ }
+ const struct type *atype = check_autodereference(ctx,
+ aexpr->slice.object->loc, expr->slice.object->result);
+ const struct type *dtype = type_dealias(ctx, atype);
+ if (dtype->storage != STORAGE_SLICE
+ && dtype->storage != STORAGE_ARRAY) {
+ error(ctx, aexpr->slice.object->loc, expr,
+ "Cannot slice non-array, non-slice object");
+ return;
+ }
+
+ const struct type *itype;
+ if (aexpr->slice.start) {
+ expr->slice.start = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->slice.start, expr->slice.start, &builtin_type_size);
+ itype = type_dealias(ctx, expr->slice.start->result);
+ if (!type_is_integer(ctx, itype)) {
+ error(ctx, aexpr->slice.start->loc, expr,
+ "Cannot use non-integer %s type as slicing operand",
+ type_storage_unparse(itype->storage));
+ return;
+ }
+ if (dtype->array.members->size == SIZE_UNDEFINED) {
+ error(ctx, aexpr->slice.start->loc, expr,
+ "Cannot use left subslicing operand on a slice with member type of unknown size");
+ return;
+ }
+
+ expr->slice.start = lower_implicit_cast(ctx,
+ &builtin_type_size, expr->slice.start);
+ }
+
+ if (aexpr->slice.end) {
+ expr->slice.end = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->slice.end, expr->slice.end, &builtin_type_size);
+ itype = type_dealias(ctx, expr->slice.end->result);
+ if (!type_is_integer(ctx, itype)) {
+ error(ctx, aexpr->slice.end->loc, expr,
+ "Cannot use non-integer %s type as slicing operand",
+ type_storage_unparse(itype->storage));
+ return;
+ }
+ expr->slice.end = lower_implicit_cast(ctx,
+ &builtin_type_size, expr->slice.end);
+ } else if (dtype->storage == STORAGE_ARRAY
+ && dtype->array.length == SIZE_UNDEFINED) {
+ error(ctx, aexpr->loc, expr,
+ "Must have end index when slicing unbounded array");
+ return;
+ }
+
+ slice_bounds_check(ctx, expr);
+
+ if (dtype->storage == STORAGE_SLICE) {
+ expr->result = atype;
+ } else {
+ expr->result = type_store_lookup_slice(ctx, aexpr->loc,
+ dtype->array.members);
+ }
+}
+
+static void
+check_struct_exhaustive(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *stype)
+{
+ stype = type_dealias(ctx, stype);
+ if (stype->storage == STORAGE_UNION) {
+ return;
+ }
+ assert(stype->storage == STORAGE_STRUCT);
+ struct struct_field *sf = stype->struct_union.fields;
+ struct ast_field_value *af = aexpr->_struct.fields;
+
+ // XXX: O(n^2)?
+ while (sf) {
+ bool found = false;
+ for (struct ast_field_value *f = af; f;
+ f = f->next) {
+ if (!sf->name) {
+ check_struct_exhaustive(ctx, aexpr, expr,
+ sf->type);
+ found = true;
+ continue;
+ }
+ if (strcmp(f->name, sf->name) == 0) {
+ if (found) {
+ error(ctx, aexpr->loc, expr,
+ "Field '%s' is initialized multiple times",
+ sf->name);
+ }
+ found = true;
+ }
+ }
+
+ bool has_default = type_has_default(ctx, sf->type)
+ || aexpr->_struct.undefined;
+ if (!found && (!aexpr->_struct.autofill || !has_default)) {
+ error(ctx, aexpr->loc, expr,
+ "Field '%s' is uninitialized",
+ sf->name);
+ }
+
+ sf = sf->next;
+ }
+}
+
+static void
+check_expr_struct(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_STRUCT;
+
+ const struct type *stype = NULL;
+ if (aexpr->_struct.type != NULL) {
+ struct scope_object *obj = scope_lookup(ctx->scope, aexpr->_struct.type);
+ // resolve the unknown type
+ wrap_resolver(ctx, obj, resolve_type);
+ if (!obj) {
+ error(ctx, aexpr->loc, expr,
+ "Unknown type alias");
+ return;
+ }
+
+ if (obj->otype != O_TYPE) {
+ error(ctx, aexpr->loc, expr,
+ "Identifier does not refer to a type");
+ return;
+ }
+ stype = obj->type;
+ enum type_storage storage = type_dealias(ctx, stype)->storage;
+ if (storage != STORAGE_STRUCT && storage != STORAGE_UNION) {
+ error(ctx, aexpr->loc, expr,
+ "Type named is not a struct or union type");
+ return;
+ }
+ }
+
+ struct ast_type satype = {
+ .storage = STORAGE_STRUCT,
+ };
+ struct ast_struct_union_field *tfield = &satype.struct_union.fields;
+ struct ast_struct_union_field **tnext = &tfield->next;
+ struct expr_struct_field *sexpr, **snext = &expr->_struct.fields;
+ expr->_struct.autofill = aexpr->_struct.autofill;
+ expr->_struct.undefined = aexpr->_struct.undefined;
+ if (stype == NULL && expr->_struct.autofill) {
+ error(ctx, aexpr->loc, expr,
+ "Autofill is only permitted for named struct initializers");
+ return;
+ }
+
+ struct ast_field_value *afield = aexpr->_struct.fields;
+ while (afield) {
+ const struct type *ftype;
+ *snext = sexpr = xcalloc(1, sizeof(struct expr_struct_field));
+ snext = &sexpr->next;
+ sexpr->value = xcalloc(1, sizeof(struct expression));
+ if (!stype) {
+ assert(afield->name); // TODO
+ if (!afield->type) {
+ error(ctx, aexpr->loc, expr,
+ "Unnamed struct must specify field type");
+ return;
+ }
+ tfield->name = afield->name;
+ tfield->type = afield->type;
+ ftype = type_store_lookup_atype(ctx, tfield->type);
+ check_expression(ctx, afield->initializer,
+ sexpr->value, ftype);
+ if (afield->next) {
+ *tnext = tfield = xcalloc(
+ 1, sizeof(struct ast_struct_union_type));
+ tnext = &tfield->next;
+ }
+ } else {
+ if (!afield->name) {
+ error(ctx, afield->initializer->loc, expr,
+ "Cannot embed a struct literal into "
+ "a named struct literal");
+ return;
+ }
+ sexpr->field = type_get_field(ctx, type_dealias(ctx, stype),
+ afield->name);
+ if (!sexpr->field) {
+ error(ctx, afield->initializer->loc, expr,
+ "No field by this name exists for this type");
+ return;
+ }
+ ftype = sexpr->field->type;
+ check_expression(ctx, afield->initializer,
+ sexpr->value, ftype);
+
+ if (!type_is_assignable(ctx, sexpr->field->type, sexpr->value->result)) {
+ char *init = gen_typename(sexpr->value->result);
+ char *bind = gen_typename(sexpr->field->type);
+ error(ctx, afield->initializer->loc, expr,
+ "Initializer of type %s not assignable to struct field of type %s",
+ init, bind);
+ free(init);
+ free(bind);
+ return;
+ }
+ sexpr->value = lower_implicit_cast(ctx,
+ sexpr->field->type, sexpr->value);
+ }
+
+ afield = afield->next;
+ }
+
+ if (stype) {
+ expr->result = stype;
+ check_struct_exhaustive(ctx, aexpr, expr, stype);
+ } else {
+ expr->result = type_store_lookup_atype(ctx, &satype);
+
+ tfield = &satype.struct_union.fields;
+ sexpr = expr->_struct.fields;
+ while (tfield) {
+ const struct struct_field *field = type_get_field(ctx,
+ expr->result, tfield->name);
+ if (!field) {
+ // TODO: Use more specific error location
+ error(ctx, aexpr->loc, expr,
+ "No field by this name exists for this type");
+ return;
+ }
+ if (!type_is_assignable(ctx, field->type, sexpr->value->result)) {
+ error(ctx, aexpr->loc, expr,
+ "Cannot initialize struct field '%s' from value of this type",
+ field->name);
+ return;
+ }
+ sexpr->field = field;
+ sexpr->value = lower_implicit_cast(ctx, field->type, sexpr->value);
+
+ struct ast_struct_union_field *next = tfield->next;
+ if (tfield != &satype.struct_union.fields) {
+ free(tfield);
+ }
+ tfield = next;
+ sexpr = sexpr->next;
+ }
+ }
+}
+
+static int
+casecmp(const void *_a, const void *_b)
+{
+ const struct expression *a = *(const struct expression **)_a;
+ const struct expression *b = *(const struct expression **)_b;
+ assert(a->type == EXPR_LITERAL && b->type == EXPR_LITERAL);
+ assert(type_dealias(NULL, a->result)->storage
+ == type_dealias(NULL, b->result)->storage);
+ if (type_is_signed(NULL, a->result)) {
+ return a->literal.ival < b->literal.ival ? -1
+ : a->literal.ival > b->literal.ival ? 1 : 0;
+ } else if (type_is_integer(NULL, a->result)) {
+ return a->literal.uval < b->literal.uval ? -1
+ : a->literal.uval > b->literal.uval ? 1 : 0;
+ } else if (type_dealias(NULL, a->result)->storage == STORAGE_STRING) {
+ size_t len = a->literal.string.len < b->literal.string.len
+ ? a->literal.string.len : b->literal.string.len;
+ int ret = memcmp(a->literal.string.value,
+ b->literal.string.value, len);
+ if (ret != 0) {
+ return ret;
+ }
+ return a->literal.string.len < b->literal.string.len ? -1
+ : a->literal.string.len > b->literal.string.len ? 1 : 0;
+ } else if (type_dealias(NULL, a->result)->storage == STORAGE_BOOL) {
+ return (int)a->literal.bval - (int)b->literal.bval;
+ } else {
+ assert(type_dealias(NULL, a->result)->storage == STORAGE_RCONST
+ || type_dealias(NULL, a->result)->storage == STORAGE_RUNE);
+ return a->literal.rune < b->literal.rune ? -1
+ : a->literal.rune > b->literal.rune ? 1 : 0;
+ }
+}
+
+static size_t
+num_cases(struct context *ctx, const struct type *type)
+{
+ type = type_dealias(ctx, type);
+ switch (type->storage) {
+ case STORAGE_BOOL:
+ return 2;
+ case STORAGE_STRING:
+ return -1;
+ case STORAGE_ENUM:;
+ struct scope_object *obj = type->_enum.values->objects;
+ assert(obj != NULL);
+ size_t n = 0;
+ for (struct scope_object *o = obj; o; o = o->lnext, ++n) {
+ if (o->otype == O_SCAN) {
+ wrap_resolver(ctx, o, resolve_enum_field);
+ }
+ assert(o->otype == O_CONST);
+ }
+ struct expression **cases_array =
+ xcalloc(n, sizeof(struct expression *));
+ size_t i = 0;
+ for (struct scope_object *o = obj; o; o = o->lnext, ++i) {
+ cases_array[i] = o->value;
+ }
+ qsort(cases_array, n, sizeof(struct expression *), &casecmp);
+ for (size_t i = 1, sz = n; i < sz; ++i) {
+ if (casecmp(&cases_array[i - 1], &cases_array[i]) == 0) {
+ --n;
+ }
+ }
+ free(cases_array);
+ return n;
+ default:
+ assert(type_is_integer(ctx, type)
+ || type->storage == STORAGE_RUNE);
+ assert(!type_is_flexible(type));
+ if (type->size >= sizeof(size_t)) {
+ return -1;
+ }
+ return (size_t)1 << (type->size * 8);
+ }
+}
+
+static void
+check_expr_switch(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_SWITCH;
+
+ struct expression *value = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->_switch.value, value, NULL);
+ const struct type *type = lower_flexible(ctx, value->result, NULL);
+ expr->_switch.value = value;
+ if (!type_is_integer(ctx, type)
+ && type_dealias(ctx, type)->storage != STORAGE_STRING
+ && type_dealias(ctx, type)->storage != STORAGE_BOOL
+ && type_dealias(ctx, type)->storage != STORAGE_RUNE) {
+ error(ctx, aexpr->loc, expr,
+ "Cannot switch on %s type",
+ type_storage_unparse(type_dealias(ctx, type)->storage));
+ return;
+ }
+
+ struct type_tagged_union tagged = { .types = NULL };
+
+ struct switch_case **next = &expr->_switch.cases, *_case = NULL;
+ size_t n = 0;
+ bool has_default_case = false;
+ struct ast_switch_case *acase;
+ for (acase = aexpr->_switch.cases; acase; acase = acase->next) {
+ _case = *next = xcalloc(1, sizeof(struct switch_case));
+ next = &_case->next;
+
+ _case->value = xcalloc(1, sizeof(struct expression));
+
+ if (acase->options == NULL) {
+ if (has_default_case) {
+ error(ctx, acase->exprs.expr->loc, _case->value,
+ "Duplicate default case");
+ }
+ has_default_case = true;
+ }
+
+ struct case_option *opt, **next_opt = &_case->options;
+ for (const struct ast_case_option *aopt = acase->options;
+ aopt; aopt = aopt->next) {
+ opt = *next_opt = xcalloc(1, sizeof(struct case_option));
+ struct expression *value =
+ xcalloc(1, sizeof(struct expression));
+ struct expression *evaled =
+ xcalloc(1, sizeof(struct expression));
+
+ check_expression(ctx, aopt->value, value, type);
+ if (!type_is_assignable(ctx, type, value->result)) {
+ char *vtype = gen_typename(value->result);
+ char *stype = gen_typename(type);
+ error(ctx, aopt->value->loc, expr,
+ "Invalid type %s for case in switch on type %s",
+ vtype, stype);
+ free(vtype);
+ free(stype);
+ return;
+ }
+ value = lower_implicit_cast(ctx, type, value);
+
+ if (!eval_expr(ctx, value, evaled)) {
+ error(ctx, aopt->value->loc, expr,
+ "Unable to evaluate case at compile time");
+ return;
+ }
+
+ opt->value = evaled;
+ next_opt = &opt->next;
+ n++;
+ }
+
+ // Lower to compound
+ // TODO: This should probably be done in a more first-class way
+ struct ast_expression compound = {
+ .type = EXPR_COMPOUND,
+ .compound = {
+ .label = aexpr->_switch.label,
+ .list = acase->exprs,
+ },
+ };
+ check_expression(ctx, &compound, _case->value, hint);
+ tagged_append(&tagged, _case->value->result);
+ }
+
+ struct expression **cases_array = xcalloc(n, sizeof(struct expression *));
+ size_t i = 0;
+ for (_case = expr->_switch.cases; _case; _case = _case->next) {
+ for (const struct case_option *opt = _case->options;
+ opt; opt = opt->next) {
+ assert(i < n);
+ if (opt->value->result->storage != STORAGE_INVALID) {
+ cases_array[i] = opt->value;
+ i++;
+ }
+ }
+ }
+ n = i;
+ qsort(cases_array, n, sizeof(struct expression *), &casecmp);
+ bool has_duplicate = false;
+ for (size_t i = 1; i < n; i++) {
+ if (casecmp(&cases_array[i - 1], &cases_array[i]) == 0) {
+ error(ctx, cases_array[i - 1]->loc, cases_array[i - 1],
+ "Duplicate switch case");
+ has_duplicate = true;
+ }
+ }
+ free(cases_array);
+ if (!has_default_case && !has_duplicate
+ && value->result->storage != STORAGE_INVALID
+ && (n == (size_t)-1 || n != num_cases(ctx, value->result))) {
+ error(ctx, aexpr->loc, value,
+ "Switch expression isn't exhaustive");
+ }
+
+ if (hint) {
+ expr->result = hint;
+ } else {
+ expr->result = type_store_reduce_result(
+ ctx, aexpr->loc, &tagged);
+ }
+
+ _case = expr->_switch.cases;
+ acase = aexpr->_switch.cases;
+ while (_case) {
+ if (!type_is_assignable(ctx, expr->result, _case->value->result)) {
+ error(ctx, acase->exprs.expr->loc, expr,
+ "Switch case is not assignable to result type");
+ return;
+ }
+ _case->value = lower_implicit_cast(ctx,
+ expr->result, _case->value);
+ _case = _case->next;
+ acase = acase->next;
+ }
+
+ free(tagged.types);
+}
+
+static void
+check_expr_tuple(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_TUPLE;
+
+ const struct type_tuple *ttuple = NULL;
+ if (hint && type_dealias(ctx, hint)->storage == STORAGE_TUPLE) {
+ ttuple = &type_dealias(ctx, hint)->tuple;
+ }
+
+ struct type_tuple result = {0};
+ struct type_tuple *rtype = &result;
+
+ struct expression_tuple *tuple = &expr->tuple;
+ for (const struct ast_expression_tuple *atuple = &aexpr->tuple;
+ atuple; atuple = atuple->next) {
+ tuple->value = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, atuple->expr, tuple->value, ttuple ? ttuple->type : NULL);
+ rtype->type = tuple->value->result;
+
+ if (atuple->next) {
+ rtype->next = xcalloc(1, sizeof(struct type_tuple));
+ rtype = rtype->next;
+ tuple->next = xcalloc(1, sizeof(struct expression_tuple));
+ tuple = tuple->next;
+ }
+
+ if (ttuple) {
+ ttuple = ttuple->next;
+ }
+ }
+
+ if (hint && type_dealias(ctx, hint)->storage == STORAGE_TUPLE) {
+ expr->result = hint;
+ } else if (hint && type_dealias(ctx, hint)->storage == STORAGE_TAGGED) {
+ const struct type *tagged = type_dealias(ctx, hint);
+ for (size_t i = 0; i < tagged->tagged.len; i++) {
+ const struct type *memb = tagged->tagged.types[i];
+ if (type_dealias(ctx, memb)->storage != STORAGE_TUPLE) {
+ continue;
+ }
+ const struct type_tuple *ttuple =
+ &type_dealias(ctx, memb)->tuple;
+ const struct expression_tuple *etuple = &expr->tuple;
+ bool valid = true;
+ while (etuple) {
+ if (!ttuple || !type_is_assignable(ctx, ttuple->type,
+ etuple->value->result)) {
+ valid = false;
+ break;
+ }
+ ttuple = ttuple->next;
+ etuple = etuple->next;
+ }
+ if (!ttuple && valid) {
+ expr->result = type_dealias(ctx, memb);
+ break;
+ }
+ }
+ if (!expr->result) {
+ error(ctx, aexpr->loc, expr,
+ "Tuple value is not assignable to tagged union hint");
+ return;
+ }
+ } else {
+ expr->result = type_store_lookup_tuple(ctx, aexpr->loc, &result);
+ if (expr->result == &builtin_type_invalid) {
+ // an error occurred
+ return;
+ }
+ }
+
+ ttuple = &type_dealias(ctx, expr->result)->tuple;
+ struct expression_tuple *etuple = &expr->tuple;
+ const struct ast_expression_tuple *atuple = &aexpr->tuple;
+ while (etuple) {
+ if (!ttuple) {
+ error(ctx, atuple->expr->loc, expr,
+ "Too many values for tuple type");
+ return;
+ }
+ if (!type_is_assignable(ctx, ttuple->type, etuple->value->result)) {
+ char *vtype = gen_typename(etuple->value->result);
+ char *ttype= gen_typename(ttuple->type);
+ error(ctx, atuple->expr->loc, expr,
+ "Value of type %s is not assignable to tuple value of type %s",
+ vtype, ttype);
+ free(vtype);
+ free(ttype);
+ return;
+ }
+ etuple->value = lower_implicit_cast(ctx, ttuple->type, etuple->value);
+ etuple = etuple->next;
+ atuple = atuple->next;
+ ttuple = ttuple->next;
+ }
+ if (ttuple) {
+ error(ctx, aexpr->loc, expr,
+ "Too few values for tuple type");
+ return;
+ }
+}
+
+static void
+check_expr_unarithm(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_UNARITHM;
+
+ struct expression *operand = xcalloc(1, sizeof(struct expression));
+ expr->unarithm.operand = operand;
+ expr->unarithm.op = aexpr->unarithm.op;
+
+ const struct type *operand_hint = NULL;
+ if (hint) {
+ switch (expr->unarithm.op) {
+ case UN_ADDRESS:
+ if (hint->storage == STORAGE_SLICE) {
+ operand_hint = type_store_lookup_array(ctx,
+ aexpr->loc, hint->array.members,
+ SIZE_UNDEFINED, false);
+ } else if (hint->storage == STORAGE_POINTER) {
+ operand_hint = hint->pointer.referent;
+ }
+ break;
+ case UN_DEREF:
+ operand_hint = type_store_lookup_pointer(
+ ctx, aexpr->loc, hint, false);
+ break;
+ default:
+ break;
+ }
+ }
+
+ check_expression(ctx, aexpr->unarithm.operand, operand, operand_hint);
+ if (operand->result->storage == STORAGE_INVALID) {
+ mkerror(expr);
+ return;
+ }
+
+ switch (expr->unarithm.op) {
+ case UN_LNOT:
+ if (type_dealias(ctx, operand->result)->storage != STORAGE_BOOL) {
+ error(ctx, aexpr->unarithm.operand->loc, expr,
+ "Cannot perform logical NOT (!) on non-boolean type");
+ return;
+ }
+ expr->result = &builtin_type_bool;
+ break;
+ case UN_BNOT:
+ if (!type_is_integer(ctx, operand->result)) {
+ error(ctx, aexpr->unarithm.operand->loc, expr,
+ "Cannot perform binary NOT (~) on non-integer type");
+ return;
+ }
+ expr->result = operand->result;
+ break;
+ case UN_MINUS:
+ if (!type_is_numeric(ctx, operand->result)) {
+ error(ctx, aexpr->unarithm.operand->loc, expr,
+ "Cannot perform operation on non-numeric type");
+ return;
+ }
+ if (operand->result->storage == STORAGE_ICONST) {
+ // Not technically quite right, but we need
+ // operand->result to be lowered with expr->result, and
+ // this is correct enough
+ const struct type *old = operand->result;
+ const struct type *new = type_create_flexible(
+ STORAGE_ICONST, -old->flexible.min,
+ -old->flexible.max);
+ lower_flexible(ctx, old, new);
+ }
+ expr->result = operand->result;
+ break;
+ case UN_ADDRESS:;
+ const struct type *ptrhint = NULL;
+ if (hint && type_dealias(ctx, hint)->storage == STORAGE_POINTER) {
+ ptrhint = type_dealias(ctx, hint)->pointer.referent;
+ if (type_dealias(ctx, ptrhint)->storage == STORAGE_OPAQUE) {
+ ptrhint = NULL;
+ }
+ }
+ if (type_is_flexible(operand->result) && ptrhint) {
+ const struct type *promoted =
+ promote_flexible(ctx, operand->result, ptrhint);
+ if (promoted) {
+ operand->result = promoted;
+ }
+ } else if (ptrhint) {
+ if (type_dealias(ctx, operand->result)
+ == type_dealias(ctx, ptrhint)) {
+ operand->result = ptrhint;
+ }
+ }
+ expr->result = type_store_lookup_pointer(
+ ctx, aexpr->loc, operand->result, false);
+ break;
+ case UN_DEREF:
+ if (type_dealias(ctx, operand->result)->storage != STORAGE_POINTER) {
+ error(ctx, aexpr->unarithm.operand->loc, expr,
+ "Cannot de-reference non-pointer type");
+ return;
+ }
+ if (type_dealias(ctx, operand->result)->pointer.nullable) {
+ error(ctx, aexpr->unarithm.operand->loc, expr,
+ "Cannot dereference nullable pointer type");
+ return;
+ }
+ if (type_dealias(ctx, operand->result)->pointer.referent->size
+ == SIZE_UNDEFINED) {
+ error(ctx, aexpr->unarithm.operand->loc, expr,
+ "Cannot dereference pointer to type of undefined size");
+ return;
+ }
+ expr->result = type_dealias(ctx, operand->result)->pointer.referent;
+ break;
+ }
+}
+
+static void
+check_expr_vastart(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ if (ctx->fntype->func.variadism != VARIADISM_C) {
+ error(ctx, aexpr->loc, expr,
+ "Cannot use vastart within function which does not use C-style variadism");
+ return;
+ }
+ expr->type = EXPR_VASTART;
+ expr->result = &builtin_type_valist;
+}
+
+static void
+check_expr_vaarg(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_VAARG;
+ expr->vaarg.ap = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->vaarg.ap, expr->vaarg.ap, &builtin_type_valist);
+ if (type_dealias(ctx, expr->vaarg.ap->result)->storage != STORAGE_VALIST) {
+ error(ctx, aexpr->loc, expr,
+ "Expected vaarg operand to be valist");
+ return;
+ }
+ expr->result = type_store_lookup_atype(ctx, aexpr->vaarg.type);
+ if (expr->result->size == SIZE_UNDEFINED) {
+ error(ctx, aexpr->loc, expr, "vaarg type must have defined size");
+ return;
+ }
+}
+
+static void
+check_expr_vaend(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->type = EXPR_VAEND;
+ expr->vaarg.ap = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->vaarg.ap, expr->vaarg.ap, &builtin_type_valist);
+ if (type_dealias(ctx, expr->vaarg.ap->result)->storage != STORAGE_VALIST) {
+ error(ctx, aexpr->loc, expr,
+ "Expected vaend operand to be valist");
+ return;
+ }
+ expr->result = &builtin_type_void;
+}
+
+void
+check_expression(struct context *ctx,
+ const struct ast_expression *aexpr,
+ struct expression *expr,
+ const struct type *hint)
+{
+ expr->loc = aexpr->loc;
+
+ switch (aexpr->type) {
+ case EXPR_ACCESS:
+ check_expr_access(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_ALLOC:
+ check_expr_alloc(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_APPEND:
+ check_expr_append_insert(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_ASSERT:
+ check_expr_assert(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_ASSIGN:
+ check_expr_assign(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_BINARITHM:
+ check_expr_binarithm(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_BINDING:
+ case EXPR_DEFINE:
+ check_expr_binding(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_BREAK:
+ case EXPR_CONTINUE:
+ case EXPR_YIELD:
+ check_expr_control(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_CALL:
+ check_expr_call(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_CAST:
+ check_expr_cast(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_COMPOUND:
+ check_expr_compound(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_LITERAL:
+ check_expr_literal(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_DEFER:
+ check_expr_defer(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_DELETE:
+ check_expr_delete(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_FOR:
+ check_expr_for(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_FREE:
+ check_expr_free(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_IF:
+ check_expr_if(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_INSERT:
+ check_expr_append_insert(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_MATCH:
+ check_expr_match(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_MEASURE:
+ check_expr_measure(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_PROPAGATE:
+ check_expr_propagate(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_RETURN:
+ check_expr_return(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_SLICE:
+ check_expr_slice(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_STRUCT:
+ check_expr_struct(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_SWITCH:
+ check_expr_switch(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_TUPLE:
+ check_expr_tuple(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_UNARITHM:
+ check_expr_unarithm(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_UNDEFINED:
+ expr->type = EXPR_UNDEFINED;
+ expr->result = &builtin_type_undefined;
+ break;
+ case EXPR_VAARG:
+ check_expr_vaarg(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_VAEND:
+ check_expr_vaend(ctx, aexpr, expr, hint);
+ break;
+ case EXPR_VASTART:
+ check_expr_vastart(ctx, aexpr, expr, hint);
+ break;
+ }
+ assert(expr->result);
+ flexible_refer(expr->result, &expr->result);
+}
+
+void
+append_decl(struct context *ctx, struct declaration *decl)
+{
+ struct declarations *decls = xcalloc(1, sizeof(struct declarations));
+ decls->decl = *decl;
+ decls->next = ctx->decls;
+ ctx->decls = decls;
+}
+
+static void
+resolve_unresolved(struct context *ctx)
+{
+ while (ctx->unresolved) {
+ struct ast_types *unresolved = ctx->unresolved;
+ ctx->unresolved = unresolved->next;
+ type_store_lookup_atype(ctx, unresolved->type);
+ free(unresolved);
+ }
+}
+
+static void
+check_function(struct context *ctx,
+ const struct scope_object *obj,
+ const struct ast_decl *adecl)
+{
+ const struct ast_function_decl *afndecl = &adecl->function;
+ ctx->fntype = obj->type;
+ if (ctx->fntype->storage == STORAGE_INVALID) {
+ return;
+ }
+
+ struct declaration _decl, *decl = &_decl;
+ decl->decl_type = DECL_FUNC;
+ decl->func.type = obj->type;
+ decl->func.flags = afndecl->flags;
+ decl->exported = adecl->exported;
+ decl->file = adecl->loc.file;
+
+ decl->symbol = ident_to_sym(ctx->itbl, obj->ident);
+ decl->ident = mkident(ctx, afndecl->ident, NULL);
+
+ if (!adecl->function.body) {
+ if (decl->func.flags != 0) {
+ error(ctx, adecl->loc, NULL,
+ "Function attributes cannot be used on prototypes");
+ return;
+ }
+ decl->func.body = NULL;
+ goto end; // Prototype
+ }
+ if (afndecl->symbol != NULL && decl->func.flags != 0) {
+ error(ctx, adecl->loc, NULL,
+ "@symbol cannot be used alongside other function attributes");
+ }
+
+ decl->func.scope = scope_push(&ctx->scope, SCOPE_FUNC);
+ struct ast_function_parameters *params = afndecl->prototype.params;
+ while (params) {
+ const struct type *type = type_store_lookup_atype(
+ ctx, params->type);
+ if (obj->type->func.variadism == VARIADISM_HARE
+ && !params->next) {
+ type = type_store_lookup_slice(ctx, params->loc, type);
+ }
+ scope_insert(decl->func.scope, O_BIND, params->name,
+ params->name, type, NULL);
+ params = params->next;
+ }
+
+ // TODO: Add function name to errors
+ if (decl->func.flags != 0) {
+ const char *flag = NULL;
+ switch (decl->func.flags) {
+ case FN_INIT:
+ flag = "@init";
+ break;
+ case FN_FINI:
+ flag = "@fini";
+ break;
+ case FN_TEST:
+ flag = "@test";
+ break;
+ default:
+ assert(0); // unreachable
+ }
+ if (obj->type->func.result != &builtin_type_void) {
+ error(ctx, adecl->loc, NULL, "%s function must return void", flag);
+ }
+ if (decl->exported) {
+ error(ctx, adecl->loc, NULL, "%s function cannot be exported", flag);
+ }
+ if (afndecl->prototype.params) {
+ error(ctx, adecl->loc, NULL, "%s function cannot have parameters", flag);
+ } else if (obj->type->func.variadism != VARIADISM_NONE) {
+ error(ctx, adecl->loc, NULL, "%s function cannot be variadic", flag);
+ }
+ }
+
+ struct expression *body = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, afndecl->body, body, obj->type->func.result);
+ resolve_unresolved(ctx);
+
+ if (!type_is_assignable(ctx, obj->type->func.result, body->result)) {
+ char *restypename = gen_typename(body->result);
+ char *fntypename = gen_typename(obj->type->func.result);
+ error(ctx, afndecl->body->loc, body,
+ "Expression result type %s is not assignable to function result type %s",
+ restypename, fntypename);
+ free(restypename);
+ free(fntypename);
+ return;
+ }
+
+ if (obj->type->func.result->storage != STORAGE_NEVER &&
+ obj->type->func.result->size == SIZE_UNDEFINED) {
+ char *fntypename = gen_typename(obj->type->func.result);
+ error(ctx, afndecl->body->loc, body,
+ "Types with undefined size such as %s cannot be returned, consider using a pointer instead",
+ fntypename);
+ free(fntypename);
+ return;
+ }
+ decl->func.body = lower_implicit_cast(ctx, obj->type->func.result, body);
+
+ scope_pop(&ctx->scope);
+ ctx->fntype = NULL;
+end:
+ if ((adecl->function.flags & FN_TEST) && !ctx->is_test) {
+ return;
+ }
+ append_decl(ctx, decl);
+}
+
+static struct scope_object *
+incomplete_decl_create(struct context *ctx, struct location loc,
+ struct scope *scope, struct ident *ident, struct ident *name)
+{
+ struct scope *subunit = ctx->unit->parent;
+ ctx->unit->parent = NULL;
+ struct scope_object *obj = scope_lookup(scope, name);
+ ctx->unit->parent = subunit;
+
+ if (obj) {
+ error_norec(ctx, loc, "Duplicate global ident '%s'",
+ ident_unparse(ident));
+ }
+ obj = scope_insert(scope, O_SCAN, ident, name, NULL, NULL);
+ obj->idecl = xcalloc(1, sizeof(struct incomplete_decl));
+ return obj;
+}
+
+static void
+scan_enum_field(struct context *ctx, struct scope *imports,
+ struct scope *enum_scope, const struct type *etype,
+ struct ast_enum_field *f)
+{
+ // We have to process the last field first
+ // This way, objects in enum_scope will have lnext pointing to
+ // the previous element, which is important for implicit enum values.
+ if (f->next) {
+ scan_enum_field(ctx, imports, enum_scope, etype, f->next);
+ }
+ assert(etype->storage == STORAGE_ENUM);
+ struct incomplete_enum_field *field =
+ xcalloc(1, sizeof(struct incomplete_enum_field));
+ *field = (struct incomplete_enum_field){
+ .field = f,
+ .enum_scope = enum_scope,
+ };
+
+ struct ident *name = intern_ident(ctx->itbl, f->name->name, etype->alias.name);
+ struct scope_object *obj = incomplete_decl_create(
+ ctx, f->loc, enum_scope, name, f->name);
+ obj->idecl->type = IDECL_ENUM_FLD;
+ obj->idecl->imports = imports;
+ obj->type = etype,
+ obj->idecl->field = field;
+}
+
+static void
+check_hosted_main(struct context *ctx,
+ struct location loc,
+ const struct ast_decl *decl,
+ struct ident *ident,
+ const char *symbol)
+{
+ if (*ctx->mainsym == '\0' || ctx->is_test) {
+ return;
+ }
+ if (symbol != ctx->mainsym && (symbol != NULL || ident != ctx->mainident)) {
+ return;
+ }
+
+ const struct ast_function_decl *func;
+ if (decl && decl->decl_type == ADECL_FUNC) {
+ func = &decl->function;
+ if (func->flags != 0) {
+ return;
+ }
+ } else {
+ error(ctx, loc, NULL,
+ "main must be a function in hosted environment");
+ return;
+ }
+
+ if (func->body != NULL && !decl->exported) {
+ error(ctx, loc, NULL,
+ "main must be exported in hosted environment");
+ return;
+ }
+ if (func->prototype.params != NULL) {
+ error(ctx, loc, NULL,
+ "main must not have parameters in hosted environment");
+ return;
+ }
+ if (func->prototype.variadism != VARIADISM_NONE) {
+ error(ctx, loc, NULL,
+ "main must not be variadic in hosted environment");
+ return;
+ }
+ if (func->prototype.result->storage != STORAGE_VOID) {
+ error(ctx, loc, NULL,
+ "main must return void in hosted environment");
+ return;
+ }
+}
+
+static void
+scan_types(struct context *ctx, struct scope *imp, const struct ast_decl *decl)
+{
+ const struct ast_type_decl *t = &decl->type;
+ struct ident *with_ns = mkident(ctx, t->ident, NULL);
+ check_hosted_main(ctx, decl->loc, NULL, with_ns, NULL);
+ struct scope_object *obj = incomplete_decl_create(ctx,
+ decl->loc, ctx->scope, with_ns, t->ident);
+ obj->idecl->decl = (struct ast_decl){
+ .decl_type = ADECL_TYPE,
+ .loc = decl->loc,
+ .type = *t,
+ .exported = decl->exported,
+ };
+ obj->idecl->imports = imp;
+ if (t->type->storage == STORAGE_ENUM) {
+ bool exported = obj->idecl->decl.exported;
+ const struct type *type = type_store_lookup_enum(
+ ctx, t->type, exported);
+ if (type->storage == STORAGE_INVALID) {
+ return; // error occured
+ }
+ scope_push((struct scope **)&type->_enum.values, SCOPE_ENUM);
+ scan_enum_field(ctx, imp,
+ type->_enum.values, type, t->type->_enum.values);
+ type->_enum.values->parent = ctx->defines;
+ obj->otype = O_TYPE;
+ obj->type = type;
+ append_decl(ctx, &(struct declaration){
+ .decl_type = DECL_TYPE,
+ .file = decl->loc.file,
+ .ident = obj->ident,
+ .exported = exported,
+ .type = type,
+ });
+ } else {
+ obj->idecl->type = IDECL_DECL;
+ }
+}
+
+static void
+unexported_type_error(struct context *ctx,
+ struct location loc, const struct type *type)
+{
+ char *s = gen_typename(type);
+ error(ctx, loc, NULL,
+ "Can't use unexported type %s in exported declaration", s);
+ free(s);
+}
+
+static void
+check_exported_type(struct context *ctx,
+ struct location loc,
+ const struct type *type)
+{
+ switch (type->storage) {
+ case STORAGE_ALIAS:
+ case STORAGE_ENUM:
+ if (!type->alias.exported) {
+ unexported_type_error(ctx, loc, type);
+ }
+ break;
+ case STORAGE_ARRAY:
+ case STORAGE_SLICE:
+ check_exported_type(ctx, loc, type->array.members);
+ break;
+ case STORAGE_ERROR:
+ check_exported_type(ctx, loc, type->error);
+ break;
+ case STORAGE_FUNCTION:
+ for (const struct type_func_param *param = type->func.params;
+ param; param = param->next) {
+ check_exported_type(ctx, loc, param->type);
+ }
+ check_exported_type(ctx, loc, type->func.result);
+ break;
+ case STORAGE_POINTER:
+ check_exported_type(ctx, loc, type->pointer.referent);
+ break;
+ case STORAGE_STRUCT:
+ case STORAGE_UNION:
+ for (const struct struct_field *field = type->struct_union.fields;
+ field; field = field->next) {
+ check_exported_type(ctx, loc, field->type);
+ }
+ break;
+ case STORAGE_TAGGED:
+ for (size_t i = 0; i < type->tagged.len; i++) {
+ check_exported_type(ctx, loc, type->tagged.types[i]);
+ }
+ break;
+ case STORAGE_TUPLE:
+ for (const struct type_tuple *t = &type->tuple; t; t = t->next) {
+ check_exported_type(ctx, loc, t->type);
+ }
+ break;
+ case STORAGE_BOOL:
+ case STORAGE_DONE:
+ case STORAGE_INVALID:
+ case STORAGE_F32:
+ case STORAGE_F64:
+ case STORAGE_FCONST:
+ case STORAGE_I16:
+ case STORAGE_I32:
+ case STORAGE_I64:
+ case STORAGE_I8:
+ case STORAGE_ICONST:
+ case STORAGE_INT:
+ case STORAGE_NEVER:
+ case STORAGE_NOMEM:
+ case STORAGE_NULL:
+ case STORAGE_OPAQUE:
+ case STORAGE_RCONST:
+ case STORAGE_RUNE:
+ case STORAGE_SIZE:
+ case STORAGE_STRING:
+ case STORAGE_U16:
+ case STORAGE_U32:
+ case STORAGE_U64:
+ case STORAGE_U8:
+ case STORAGE_UINT:
+ case STORAGE_UINTPTR:
+ case STORAGE_VALIST:
+ case STORAGE_VOID:
+ case STORAGE_UNDEFINED:
+ break;
+ }
+}
+
+static void
+resolve_const(struct context *ctx, struct scope_object *obj)
+{
+ const struct ast_global_decl *decl = &obj->idecl->decl.constant;
+
+ assert(!decl->symbol); // Invariant
+
+ const struct type *type = NULL;
+ if (decl->type) {
+ type = type_store_lookup_atype(ctx, decl->type);
+ }
+ struct expression *init = xcalloc(1, sizeof(struct expression));
+ obj->value = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, decl->init, init, type);
+ if (!decl->type) {
+ type = init->result;
+ }
+ if (obj->idecl->decl.exported) {
+ struct location loc =
+ decl->type ? decl->type->loc : decl->init->loc;
+ check_exported_type(ctx, loc, type);
+ }
+ if (!type_is_assignable(ctx, type, init->result)) {
+ char *typename1 = gen_typename(init->result);
+ char *typename2 = gen_typename(type);
+ error(ctx, decl->init->loc, obj->value,
+ "Initializer type %s is not assignable to constant type %s",
+ typename1, typename2);
+ free(typename1);
+ free(typename2);
+ type = &builtin_type_invalid;
+ goto end;
+ }
+ if (decl->type) {
+ if (decl->type->storage == STORAGE_ARRAY
+ && decl->type->array.contextual) {
+ type = lower_flexible(ctx, init->result, NULL);
+ } else {
+ init = lower_implicit_cast(ctx, type, init);
+ }
+ }
+
+ if (!eval_expr(ctx, init, obj->value)) {
+ error(ctx, decl->init->loc, obj->value,
+ "Unable to evaluate initializer at compile time");
+ type = &builtin_type_invalid;
+ goto end;
+ }
+end:
+ obj->otype = O_CONST;
+
+ if (!ctx->defines || ctx->errors) {
+ return;
+ }
+ struct scope_object *shadow_obj = scope_lookup(ctx->defines, obj->ident);
+ if (shadow_obj && obj != shadow_obj) {
+ // Shadowed by define
+ if (type_is_flexible(obj->value->result)
+ || type_is_flexible(shadow_obj->value->result)) {
+ const struct type *promoted = promote_flexible(ctx,
+ obj->value->result, shadow_obj->value->result);
+ if (promoted == NULL) {
+ const char *msg;
+ char *typename = NULL;
+ if (!type_is_flexible(obj->value->result)) {
+ msg = "Constant of type %s is shadowed by define of incompatible flexible type";
+ typename = gen_typename(obj->value->result);
+ } else if (!type_is_flexible(shadow_obj->value->result)) {
+ msg = "Constant of flexible type is shadowed by define of incompatible type %s";
+ typename = gen_typename(shadow_obj->value->result);
+ } else {
+ msg = "Constant of flexible type is shadowed by define of incompatible flexible type";
+ }
+ error(ctx, obj->idecl->decl.loc, NULL, msg, typename);
+ free(typename);
+ } else {
+ shadow_obj->value = lower_implicit_cast(ctx,
+ promoted, shadow_obj->value);
+ }
+ } else if (obj->value->result != shadow_obj->value->result) {
+ char *typename = gen_typename(obj->value->result);
+ char *shadow_typename = gen_typename(shadow_obj->value->result);
+ error(ctx, obj->idecl->decl.loc, NULL,
+ "Constant of type %s is shadowed by define of incompatible type %s",
+ typename, shadow_typename);
+ free(typename);
+ free(shadow_typename);
+ }
+ obj->value = shadow_obj->value;
+ }
+ append_decl(ctx, &(struct declaration){
+ .decl_type = DECL_CONST,
+ .file = obj->idecl->decl.loc.file,
+ .ident = obj->ident,
+ .exported = obj->idecl->decl.exported,
+ .constant = {
+ .type = type,
+ .value = obj->value,
+ }
+ });
+}
+
+static void
+resolve_function(struct context *ctx, struct scope_object *obj)
+{
+ const struct ast_function_decl *decl = &obj->idecl->decl.function;
+
+ const struct ast_type fn_atype = {
+ .loc = obj->idecl->decl.loc,
+ .storage = STORAGE_FUNCTION,
+ .func = decl->prototype,
+ };
+ const struct type *fntype = type_store_lookup_atype(ctx, &fn_atype);
+ if (obj->idecl->decl.exported) {
+ check_exported_type(ctx, obj->idecl->decl.loc, fntype);
+ }
+
+ obj->otype = O_DECL;
+ obj->type = fntype;
+}
+
+static void
+resolve_global(struct context *ctx, struct scope_object *obj)
+{
+ const struct ast_global_decl *decl = &obj->idecl->decl.global;
+ const struct type *type = NULL;
+ bool context = false;
+ struct expression *init, *value = NULL;
+ if (decl->type) {
+ type = type_store_lookup_atype(ctx, decl->type);
+ if (type->storage == STORAGE_NEVER) {
+ error(ctx, decl->type->loc, NULL,
+ "Global cannot have type never");
+ type = &builtin_type_invalid;
+ goto end;
+ }
+ context = decl->type->storage == STORAGE_ARRAY
+ && decl->type->array.contextual;
+ if (context && !decl->init) {
+ error(ctx, decl->type->loc, NULL,
+ "Cannot infer array length without an initializer");
+ type = &builtin_type_invalid;
+ goto end;
+ }
+ }
+
+ if (decl->init) {
+ init = xcalloc(1, sizeof(struct expression));
+ value = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, decl->init, init, type);
+ if (type) {
+ if (!type_is_assignable(ctx, type, init->result)) {
+ char *typename1 = gen_typename(init->result);
+ char *typename2 = gen_typename(type);
+ error(ctx, decl->init->loc, value,
+ "Initializer type %s is not assignable to global type %s",
+ typename1, typename2);
+ free(typename1);
+ free(typename2);
+ type = &builtin_type_invalid;
+ goto end;
+ }
+ } else {
+ type = lower_flexible(ctx, init->result, NULL);
+ }
+ if (context) {
+ type = init->result;
+ } else {
+ init = lower_implicit_cast(ctx, type, init);
+ }
+ if (type->storage == STORAGE_NEVER) {
+ error(ctx, obj->idecl->decl.loc, NULL,
+ "Global cannot have type never");
+ type = &builtin_type_invalid;
+ goto end;
+ }
+ if (type->size == SIZE_UNDEFINED) {
+ error(ctx, decl->init->loc, NULL,
+ "Cannot initialize object with undefined size");
+ type = &builtin_type_invalid;
+ goto end;
+ }
+ if (type->storage == STORAGE_NULL) {
+ error(ctx, decl->init->loc, NULL,
+ "Can't initialize global as null without explicit type hint");
+ type = &builtin_type_invalid;
+ goto end;
+ }
+ if (!eval_expr(ctx, init, value)) {
+ error(ctx, decl->init->loc, value,
+ "Unable to evaluate initializer at compile time");
+ type = &builtin_type_invalid;
+ goto end;
+ }
+ }
+
+ if (obj->idecl->decl.exported) {
+ struct location loc =
+ decl->type ? decl->type->loc : decl->init->loc;
+ check_exported_type(ctx, loc, type);
+ }
+
+end:;
+ struct ident *name = mkident(ctx, obj->name, NULL);
+ obj->otype = O_DECL;
+ obj->type = type;
+ if (decl->threadlocal) {
+ obj->flags |= SO_THREADLOCAL;
+ }
+
+ append_decl(ctx, &(struct declaration){
+ .decl_type = DECL_GLOBAL,
+ .file = obj->idecl->decl.loc.file,
+ .ident = name,
+ .symbol = ident_to_sym(ctx->itbl, obj->ident),
+
+ .exported = obj->idecl->decl.exported,
+ .global = {
+ .type = type,
+ .value = value,
+ .threadlocal = obj->idecl->decl.global.threadlocal,
+ }
+ });
+}
+
+static void
+resolve_enum_field(struct context *ctx, struct scope_object *obj)
+{
+ assert(obj->idecl->type == IDECL_ENUM_FLD);
+
+ const struct type *type = obj->type;
+
+ struct ident *localname = intern_name(ctx->itbl, obj->ident->name);
+ struct scope_object *new =
+ scope_lookup(obj->idecl->field->enum_scope, localname);
+ if (new != obj) {
+ wrap_resolver(ctx, new, resolve_enum_field);
+ assert(new->otype == O_CONST);
+ obj->otype = O_CONST;
+ obj->value = new->value;
+ return;
+ }
+
+ ctx->scope = obj->idecl->field->enum_scope;
+ obj->value = xcalloc(1, sizeof(struct expression));
+ obj->value->result = type;
+ if (obj->idecl->field->field->value) { // explicit value
+ struct expression *initializer =
+ xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, obj->idecl->field->field->value,
+ initializer, type->alias.type);
+
+ if (!type_is_assignable(ctx, type->alias.type, initializer->result)) {
+ char *inittypename = gen_typename(initializer->result);
+ char *builtintypename = gen_typename(type->alias.type);
+ error_norec(ctx, obj->idecl->field->field->value->loc,
+ "Enum value type (%s) is not assignable from initializer type (%s) for value %s",
+ builtintypename, inittypename, obj->ident->name);
+ }
+
+ initializer = lower_implicit_cast(ctx, type, initializer);
+ if (!eval_expr(ctx, initializer, obj->value)) {
+ error_norec(ctx, obj->idecl->field->field->value->loc,
+ "Unable to evaluate constant initializer at compile time");
+ }
+ } else { // implicit value
+ struct scope_object *next = obj->lnext;
+ // find previous enum value
+ wrap_resolver(ctx, next, resolve_enum_field);
+ obj->value->type = EXPR_LITERAL;
+ if (type_is_signed(ctx, type_dealias(ctx, type))) {
+ if (next == NULL) {
+ obj->value->literal.ival = 0;
+ } else {
+ obj->value->literal.ival = next->value->literal.ival + 1;
+ }
+ } else {
+ if (next == NULL) {
+ obj->value->literal.uval = 0;
+ } else {
+ obj->value->literal.uval = next->value->literal.uval + 1;
+ }
+ }
+ }
+ obj->otype = O_CONST;
+}
+
+static const struct type *
+lookup_enum_type(struct context *ctx, const struct scope_object *obj)
+{
+ const struct type *enum_type = NULL;
+
+ switch (obj->otype) {
+ case O_SCAN: {
+ if (obj->idecl->in_progress) {
+ // Type alias cycle will be handled in check
+ return NULL;
+ }
+
+ if (obj->idecl->type != IDECL_DECL ||
+ obj->idecl->decl.decl_type != ADECL_TYPE) {
+ return NULL;
+ }
+
+ if (obj->idecl->decl.type.type->storage == STORAGE_ENUM) {
+ assert(false);
+ } else if (obj->idecl->decl.type.type->storage == STORAGE_ALIAS) {
+ ctx->scope->parent = obj->idecl->imports;
+ const struct scope_object *new = scope_lookup(ctx->scope,
+ obj->idecl->decl.type.type->alias);
+ if (new) {
+ obj->idecl->in_progress = true;
+ enum_type = lookup_enum_type(ctx, new);
+ obj->idecl->in_progress = false;
+ }
+ }
+ break;
+ }
+ case O_TYPE:
+ enum_type = obj->type;
+ break;
+ default:
+ return NULL;
+ }
+
+ if (!enum_type) {
+ return NULL;
+ }
+
+ enum_type = type_dealias(ctx, enum_type);
+ if (enum_type->storage != STORAGE_ENUM) {
+ return NULL;
+ }
+ return enum_type;
+}
+
+static void
+scan_enum_field_aliases(struct context *ctx, struct scope_object *obj)
+{
+ const struct type *enum_type = lookup_enum_type(ctx, obj);
+
+ if (!enum_type) {
+ return;
+ }
+
+ // orig->type is (perhaps transitively) an alias of a resolved enum
+ // type, which means its dependency graph is a linear chain of
+ // resolved types ending with that enum, so we can immediately resolve it
+ wrap_resolver(ctx, obj, resolve_type);
+
+ for (const struct scope_object *val = enum_type->_enum.values->objects;
+ val; val = val->lnext) {
+ struct ast_enum_field *afield =
+ xcalloc(1, sizeof(struct ast_enum_field));
+ *afield = (struct ast_enum_field){
+ .loc = (struct location){0}, // XXX: what to put here?
+ .name = (struct ident *)val->name,
+ };
+
+ struct incomplete_enum_field *field =
+ xcalloc(1, sizeof(struct incomplete_enum_field));
+ *field = (struct incomplete_enum_field){
+ .field = afield,
+ .enum_scope = val->idecl->field->enum_scope,
+ };
+
+ struct ident *name =
+ intern_ident(ctx->itbl, val->name->name, obj->name);
+ struct scope_object *new = incomplete_decl_create(ctx,
+ (struct location){0}, ctx->scope, name, name);
+ new->idecl->type = IDECL_ENUM_FLD;
+ new->type = obj->type;
+ new->idecl->field = field;
+ }
+}
+
+void
+resolve_dimensions(struct context *ctx, struct scope_object *obj)
+{
+ if (obj->idecl->type != IDECL_DECL || obj->idecl->decl.decl_type != ADECL_TYPE) {
+ struct location loc;
+ if (obj->idecl->type == IDECL_ENUM_FLD) {
+ loc = obj->idecl->field->field->loc;
+ } else {
+ loc = obj->idecl->decl.loc;
+ }
+ char *ident = ident_unparse(obj->name);
+ error(ctx, loc, NULL, "'%s' is not a type", ident);
+ free(ident);
+ obj->type = &builtin_type_invalid;
+ return;
+ }
+ struct dimensions dim = type_store_lookup_dimensions(ctx,
+ obj->idecl->decl.type.type);
+ obj->type = xcalloc(1, sizeof(struct type));
+ *(struct type *)obj->type = (struct type){
+ .size = dim.size,
+ .align = dim.align,
+ };
+}
+
+void
+resolve_type(struct context *ctx, struct scope_object *obj)
+{
+ struct location loc;
+ if (obj->idecl->type == IDECL_ENUM_FLD) {
+ loc = obj->idecl->field->field->loc;
+ } else {
+ loc = obj->idecl->decl.loc;
+ }
+
+ if (obj->idecl->type != IDECL_DECL || obj->idecl->decl.decl_type != ADECL_TYPE) {
+ error_norec(ctx, loc, "'%s' is not a type",
+ ident_unparse(obj->name));
+ }
+
+ // compute type dimensions
+ struct errors **cur_err = ctx->next;
+ struct dimensions dim = type_store_lookup_dimensions(
+ ctx, obj->idecl->decl.type.type);
+ obj->idecl->in_progress = false;
+
+ // compute type representation and store it
+ struct type *alias = (struct type *)type_store_lookup_alias(ctx, obj->ident,
+ obj->name, NULL, obj->idecl->decl.exported);
+ obj->otype = O_TYPE;
+ obj->type = alias;
+ if (ctx->next == cur_err) {
+ alias->size = dim.size;
+ alias->align = dim.align;
+ alias->alias.type = type_store_lookup_atype(
+ ctx, obj->idecl->decl.type.type);
+ } else {
+ alias->alias.type = &builtin_type_invalid;
+ }
+ assert(alias->alias.type != NULL);
+ if (obj->idecl->decl.exported) {
+ check_exported_type(ctx, obj->idecl->decl.type.type->loc,
+ alias->alias.type);
+ }
+ if (alias->alias.type->storage == STORAGE_NEVER) {
+ error(ctx, loc, NULL, "Can't declare type alias of never");
+ alias->alias.type = &builtin_type_invalid;
+ }
+
+ append_decl(ctx, &(struct declaration){
+ .decl_type = DECL_TYPE,
+ .file = obj->idecl->decl.loc.file,
+ .ident = obj->ident,
+ .exported = obj->idecl->decl.exported,
+ .type = alias,
+ });
+}
+
+static struct scope_object *
+scan_const(struct context *ctx, struct scope *imports, bool exported,
+ struct location loc, const struct ast_global_decl *decl)
+{
+ struct ident *with_ns = mkident(ctx, decl->ident, NULL);
+ check_hosted_main(ctx, loc, NULL, with_ns, NULL);
+ struct scope_object *obj = incomplete_decl_create(ctx, loc,
+ ctx->scope, with_ns, decl->ident);
+ obj->idecl->type = IDECL_DECL;
+ obj->idecl->decl = (struct ast_decl){
+ .decl_type = ADECL_CONST,
+ .loc = loc,
+ .constant = *decl,
+ .exported = exported,
+ };
+ obj->idecl->imports = imports;
+ return obj;
+}
+
+static void
+scan_decl(struct context *ctx, struct scope *imports, const struct ast_decl *decl)
+{
+ struct scope_object *obj;
+ struct ident *ident;
+ switch (decl->decl_type) {
+ case ADECL_CONST:
+ scan_const(ctx, imports, decl->exported, decl->loc, &decl->constant);
+ break;
+ case ADECL_GLOBAL:
+ ident = mkident(ctx, decl->global.ident, decl->global.symbol);
+ check_hosted_main(ctx, decl->loc, NULL, ident, decl->global.symbol);
+ obj = incomplete_decl_create(ctx, decl->loc,
+ ctx->scope, ident, decl->global.ident);
+ obj->idecl->type = IDECL_DECL;
+ obj->idecl->decl = (struct ast_decl){
+ .decl_type = ADECL_GLOBAL,
+ .loc = decl->loc,
+ .global = decl->global,
+ .exported = decl->exported,
+ };
+ obj->idecl->imports = imports;
+ break;
+ case ADECL_FUNC:;
+ const struct ast_function_decl *func = &decl->function;
+ struct ident *name;
+ if (func->flags) {
+ const char *template = NULL;
+ if (func->flags & FN_TEST) {
+ template = "testfunc.%d";
+ } else if (func->flags & FN_INIT) {
+ template = "initfunc.%d";
+ } else if (func->flags & FN_FINI) {
+ template = "finifunc.%d";
+ }
+ assert(template);
+ ident = name = intern_generated(ctx, template);
+ } else {
+ ident = mkident(ctx, func->ident, func->symbol);
+ name = func->ident;
+ }
+ obj = incomplete_decl_create(ctx, decl->loc,
+ ctx->scope, ident, name);
+ check_hosted_main(ctx, decl->loc, decl, ident, func->symbol);
+ obj->idecl->type = IDECL_DECL;
+ obj->idecl->decl = (struct ast_decl){
+ .decl_type = ADECL_FUNC,
+ .loc = decl->loc,
+ .function = *func,
+ .exported = decl->exported,
+ };
+ obj->idecl->imports = imports;
+ break;
+ case ADECL_TYPE:
+ scan_types(ctx, imports, decl);
+ break;
+ case ADECL_ASSERT:;
+ struct ident *id = intern_generated(ctx, "static_assert.%d");
+ obj = incomplete_decl_create(ctx, decl->loc, ctx->scope, id, id);
+ obj->idecl->type = IDECL_DECL;
+ obj->idecl->decl = (struct ast_decl){
+ .decl_type = ADECL_ASSERT,
+ .loc = decl->loc,
+ .assert = decl->assert,
+ .exported = decl->exported,
+ };
+ obj->idecl->imports = imports;
+ break;
+ }
+}
+
+static void
+resolve_decl(struct context *ctx, struct scope_object *obj)
+{
+ switch (obj->idecl->type) {
+ case IDECL_ENUM_FLD:
+ resolve_enum_field(ctx, obj);
+ return;
+ case IDECL_DECL:
+ break;
+ }
+
+ switch (obj->idecl->decl.decl_type) {
+ case ADECL_CONST:
+ resolve_const(ctx, obj);
+ return;
+ case ADECL_GLOBAL:
+ resolve_global(ctx, obj);
+ return;
+ case ADECL_FUNC:
+ resolve_function(ctx, obj);
+ return;
+ case ADECL_TYPE:
+ resolve_type(ctx, obj);
+ return;
+ case ADECL_ASSERT:;
+ struct expression expr = {0};
+ check_assert(ctx, obj->idecl->decl.assert, obj->idecl->decl.loc, &expr);
+ return;
+ }
+ abort();
+}
+
+void
+wrap_resolver(struct context *ctx, struct scope_object *obj, resolvefn resolver)
+{
+ // ensure this declaration wasn't already scanned
+ if (!obj || obj->otype != O_SCAN) {
+ return;
+ }
+
+ // save current subunit and enum context
+ struct scope *scope = ctx->scope;
+ struct scope *subunit = ctx->unit->parent;
+ ctx->unit->parent = NULL;
+ const struct type *fntype = ctx->fntype;
+ ctx->fntype = NULL;
+ struct ast_types *unresolved = ctx->unresolved;
+ ctx->unresolved = NULL;
+
+ // load this declaration's subunit context
+ ctx->scope = ctx->defines;
+ ctx->unit->parent = obj->idecl->imports;
+
+ // resolving a declaration that is already in progress -> cycle
+ if (obj->idecl->in_progress) {
+ struct location loc;
+ if (obj->idecl->type == IDECL_ENUM_FLD) {
+ loc = obj->idecl->field->field->loc;
+ } else {
+ loc = obj->idecl->decl.loc;
+ }
+ error_norec(ctx, loc, "Circular dependency for '%s'",
+ ident_unparse(obj->name));
+ }
+ obj->idecl->in_progress = true;
+
+ resolver(ctx, obj);
+
+ obj->idecl->in_progress = false;
+ resolve_unresolved(ctx);
+ // load stored context
+ ctx->unresolved = unresolved;
+ ctx->fntype = fntype;
+ ctx->unit->parent = subunit;
+ ctx->scope = scope;
+}
+
+static void
+load_import(struct context *ctx, const struct ast_decls *defines,
+ struct ast_imports *import, struct scope *scope)
+{
+ struct scope *mod = module_resolve(ctx, defines, import->ident);
+
+ if (import->mode == IMPORT_MEMBERS) {
+ for (const struct ast_import_members *member = import->members;
+ member; member = member->next) {
+ struct ident *ident = intern_ident(ctx->itbl,
+ member->name->name, import->ident);
+ const struct scope_object *obj = scope_lookup(mod, ident);
+ if (!obj) {
+ error_norec(ctx, member->loc, "Unknown object '%s'",
+ ident_unparse(ident));
+ }
+ assert(obj->otype != O_SCAN);
+ // obj->type and obj->value are a union, so it doesn't
+ // matter which is passed into scope_insert
+ struct scope_object *new = scope_insert(scope,
+ obj->otype, obj->ident, member->name, obj->type, NULL);
+ new->flags = obj->flags;
+ if (obj->otype != O_TYPE
+ || type_dealias(ctx, obj->type)->storage
+ != STORAGE_ENUM) {
+ continue;
+ }
+ const struct scope *enum_scope =
+ type_dealias(ctx, obj->type)->_enum.values;
+ for (const struct scope_object *o = enum_scope->objects;
+ o; o = o->lnext) {
+ struct ident *value_ident =
+ intern_ident(ctx->itbl, o->name->name, ident);
+ struct ident *value_name =
+ intern_ident(ctx->itbl, o->name->name, member->name);
+ scope_insert(scope, o->otype, value_ident,
+ value_name, NULL, o->value);
+ }
+ }
+ return;
+ }
+
+ struct ident *prefix = NULL;
+ switch (import->mode) {
+ case IMPORT_NORMAL:
+ prefix = intern_name(ctx->itbl, import->ident->name);
+ break;
+ case IMPORT_ALIAS:
+ prefix = intern_name(ctx->itbl, import->alias);
+ break;
+ case IMPORT_WILDCARD:
+ prefix = NULL;
+ break;
+ case IMPORT_MEMBERS:
+ abort(); // Unreachable
+ }
+
+ for (const struct scope_object *obj = mod->objects;
+ obj; obj = obj->lnext) {
+ assert(obj->otype != O_SCAN);
+
+ struct scope_object *new;
+ if (import->mode == IMPORT_NORMAL) {
+ // obj->type and obj->value are a union, so it doesn't
+ // matter which is passed into scope_insert
+ new = scope_insert(scope, obj->otype, obj->ident,
+ obj->name, obj->type, NULL);
+ new->flags = obj->flags;
+ }
+
+ struct ident *name;
+ if (obj->name->ns == NULL) {
+ // this is only possible if an invalid .td file is used.
+ // this check is necessary since the scope_lookup below
+ // will segfault if obj->name.ns is NULL
+ error_norec(ctx, (struct location){0},
+ "Invalid typedefs for %s",
+ ident_unparse(import->ident));
+ }
+ const struct scope_object *_enum = scope_lookup(mod, obj->name->ns);
+ if (_enum != NULL && _enum->otype == O_TYPE
+ && type_dealias(NULL, _enum->type)->storage == STORAGE_ENUM) {
+ // include enum type in ident if object is an enum
+ // constant
+ struct ident *ns =
+ intern_ident(ctx->itbl, obj->name->ns->name, prefix);
+ name = intern_ident(ctx->itbl, obj->name->name, ns);
+ } else {
+ name = intern_ident(ctx->itbl, obj->name->name, prefix);
+ }
+ // obj->type and obj->value are a union, so it doesn't matter
+ // which is passed into scope_insert
+ new = scope_insert(scope, obj->otype, obj->ident, name,
+ obj->type, NULL);
+ new->flags = obj->flags;
+ }
+}
+
+static const struct location defineloc = {
+ .file = 0,
+ .lineno = 1,
+ .colno = 1,
+};
+
+struct scope *
+check_internal(type_store *ts,
+ struct modcache **cache,
+ bool is_test,
+ const char *mainsym,
+ struct ident *mainident,
+ const struct ast_decls *defines,
+ const struct ast_unit *aunit,
+ struct unit *unit,
+ struct intern_table *itbl,
+ bool scan_only)
+{
+ struct context ctx = {0};
+ ctx.ns = unit->ns;
+ ctx.is_test = is_test;
+ ctx.mainsym = mainsym;
+ ctx.mainident = mainident;
+ ctx.store = ts;
+ ctx.next = &ctx.errors;
+ ctx.modcache = cache;
+ ctx.itbl = itbl;
+
+ // Top-level scope management involves:
+ //
+ // - Creating a top-level scope for the whole unit, to which
+ // declarations are added.
+ // - Creating a scope for each sub-unit, and populating it with imports.
+ //
+ // Further down the call frame, subsequent functions will create
+ // sub-scopes for each declaration, expression-list, etc.
+
+ // Put defines into a temporary scope (-D on the command line)
+ sources[0] = "-D";
+ ctx.scope = NULL;
+ ctx.unit = scope_push(&ctx.scope, SCOPE_DEFINES);
+ for (const struct ast_decls *def = defines; def; def = def->next) {
+ const struct ast_decl *decl = &def->decl;
+ assert(decl->decl_type == ADECL_CONST);
+ struct scope_object *obj =
+ scan_const(&ctx, NULL, false, defineloc, &decl->constant);
+ resolve_const(&ctx, obj);
+ }
+ ctx.defines = ctx.scope;
+ ctx.scope = NULL;
+ ctx.defines->parent = ctx.unit = scope_push(&ctx.scope, SCOPE_UNIT);
+ sources[0] = "";
+
+ // Populate the imports and put declarations into a scope.
+ // Each declaration holds a reference to its subunit's imports
+ // A scope gets us:
+ // a) duplicate detection for free
+ // b) a way to find declaration's definition when it's refered to
+ struct scopes *subunit_scopes = NULL, **next = &subunit_scopes;
+ struct scope *su_scope = NULL;
+ struct identifiers **inext = &unit->imports;
+ for (const struct ast_subunit *su = &aunit->subunits;
+ su; su = su->next) {
+ su_scope = NULL;
+ scope_push(&su_scope, SCOPE_SUBUNIT);
+ for (struct ast_imports *imports = su->imports;
+ imports; imports = imports->next) {
+ load_import(&ctx, defines, imports, su_scope);
+
+ bool found = false;
+ for (struct identifiers *uimports = unit->imports;
+ uimports; uimports = uimports->next) {
+ if (uimports->ident == imports->ident) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ struct identifiers *uimport = *inext =
+ xcalloc(1, sizeof(struct identifiers));
+ uimport->ident = imports->ident;
+ inext = &uimport->next;
+ }
+ }
+
+ for (struct ast_decls *d = su->decls; d; d = d->next) {
+ scan_decl(&ctx, su_scope, &d->decl);
+ }
+
+ *next = xcalloc(1, sizeof(struct scopes));
+ (*next)->scope = su_scope;
+ next = &(*next)->next;
+ }
+
+ // Find enum aliases and store them in incomplete enum value declarations
+ for (struct scope_object *obj = ctx.scope->objects;
+ obj; obj = obj->lnext) {
+ scan_enum_field_aliases(&ctx, obj);
+ }
+
+ // XXX: shadowed declarations are not checked for consistency
+ ctx.scope = ctx.defines;
+
+ for (const struct scope_object *obj = ctx.scope->objects;
+ obj; obj = obj->lnext) {
+ const struct scope_object *shadowed_obj =
+ scope_lookup(ctx.unit, obj->name);
+ if (!shadowed_obj) {
+ continue;
+ }
+ if (shadowed_obj->otype == O_CONST) {
+ continue;
+ }
+ if (shadowed_obj->otype == O_SCAN) {
+ if (shadowed_obj->idecl->type == IDECL_DECL &&
+ shadowed_obj->idecl->decl.decl_type == ADECL_CONST) {
+ continue;
+ }
+ }
+ error(&ctx, defineloc, NULL, "Define shadows a non-define object");
+ }
+
+ // Perform actual declaration resolution
+ for (struct scope_object *obj = ctx.unit->objects;
+ obj; obj = obj->lnext) {
+ wrap_resolver(&ctx, obj, resolve_decl);
+ // populate the expression graph
+ if (obj->idecl->type == IDECL_DECL && obj->idecl->decl.decl_type == ADECL_FUNC) {
+ ctx.unit->parent = obj->idecl->imports;
+ check_function(&ctx, obj, &obj->idecl->decl);
+ }
+ }
+
+ assert(ctx.unresolved == NULL);
+ handle_errors(ctx.errors);
+ unit->declarations = ctx.decls;
+
+ if (!(scan_only || unit->declarations)) {
+ xfprintf(stderr, "Error: module contains no declarations\n");
+ exit(EXIT_CHECK);
+ }
+
+ ctx.unit->parent = NULL;
+ return ctx.unit;
+}
+
+struct scope *
+check(type_store *ts,
+ bool is_test,
+ const char *mainsym,
+ struct ident *mainident,
+ const struct ast_decls *defines,
+ const struct ast_unit *aunit,
+ struct unit *unit,
+ struct intern_table *itbl)
+{
+ struct modcache *modcache[MODCACHE_BUCKETS] = {0};
+ return check_internal(ts, modcache, is_test, mainsym, mainident, defines, aunit, unit, itbl, false);
+}
diff --git a/ref/harec/src/eval.c b/ref/harec/src/eval.c
new file mode 100644
index 00000000..f1e0fd47
--- /dev/null
+++ b/ref/harec/src/eval.c
@@ -0,0 +1,1304 @@
+#include
+#include
+#include
+#include
+#include
+#include "check.h"
+#include "eval.h"
+#include "expr.h"
+#include "scope.h"
+#include "type_store.h"
+#include "types.h"
+#include "util.h"
+
+static bool
+eval_access(struct context *ctx,
+ const struct expression *restrict in,
+ struct expression *restrict out)
+{
+ struct expression tmp = {0};
+ switch (in->access.type) {
+ case ACCESS_IDENTIFIER:
+ return false; // &ident handled in eval_unarithm
+ case ACCESS_INDEX:
+ if (!eval_expr(ctx, in->access.array, &tmp)) {
+ return false;
+ }
+ const struct array_literal *array = tmp.literal.array;
+ if (!eval_expr(ctx, in->access.index, &tmp)) {
+ return false;
+ }
+ for (size_t i = tmp.literal.uval; i > 0; --i) {
+ if (array == NULL) {
+ error(ctx, in->loc, NULL,
+ "slice or array access out of bounds");
+ return false;
+ }
+ array = array->next;
+ }
+ return eval_expr(ctx, array->value, out);
+ case ACCESS_FIELD:
+ if (!eval_expr(ctx, in->access._struct, &tmp)) {
+ return false;
+ }
+ const struct struct_literal *fields = tmp.literal._struct;
+ for (; fields != NULL; fields = fields->next) {
+ if (!strcmp(fields->field->name, in->access.field->name)) {
+ break;
+ }
+ }
+ if (fields == NULL) {
+ return false;
+ }
+ return eval_expr(ctx, fields->value, out);
+ case ACCESS_TUPLE:
+ if (!eval_expr(ctx, in->access.tuple, &tmp)) {
+ return false;
+ }
+ const struct tuple_literal *tuple = tmp.literal.tuple;
+ for (size_t i = in->access.tindex; i > 0; --i) {
+ if (tuple == NULL) {
+ // out of bounds
+ return false;
+ }
+ tuple = tuple->next;
+ }
+ return eval_expr(ctx, tuple->value, out);
+ }
+
+ return true;
+}
+
+static uint64_t
+itrunc(struct context *ctx, const struct type *type, uint64_t val)
+{
+ switch (type->storage) {
+ case STORAGE_U8:
+ return (uint8_t)val;
+ case STORAGE_U16:
+ return (uint16_t)val;
+ case STORAGE_U32:
+ case STORAGE_RCONST:
+ case STORAGE_RUNE:
+ return (uint32_t)val;
+ case STORAGE_U64:
+ return (uint64_t)val;
+ case STORAGE_I8:
+ return (int8_t)val;
+ case STORAGE_I16:
+ return (int16_t)val;
+ case STORAGE_I32:
+ return (int32_t)val;
+ case STORAGE_I64:
+ return (int64_t)val;
+ case STORAGE_INT:
+ return (int)val;
+ case STORAGE_UINT:
+ return (unsigned int)val;
+ case STORAGE_ARRAY:
+ case STORAGE_ICONST:
+ case STORAGE_SIZE:
+ case STORAGE_UINTPTR:
+ return val;
+ case STORAGE_NULL:
+ return 0;
+ case STORAGE_ERROR:
+ case STORAGE_ALIAS:
+ return itrunc(ctx, type_dealias(ctx, type), val);
+ case STORAGE_ENUM:
+ return itrunc(ctx, type->alias.type, val);
+ case STORAGE_INVALID:
+ return val;
+ case STORAGE_BOOL:
+ case STORAGE_DONE:
+ case STORAGE_F32:
+ case STORAGE_F64:
+ case STORAGE_FCONST:
+ case STORAGE_FUNCTION:
+ case STORAGE_NEVER:
+ case STORAGE_NOMEM:
+ case STORAGE_OPAQUE:
+ case STORAGE_POINTER:
+ case STORAGE_SLICE:
+ case STORAGE_STRING:
+ case STORAGE_STRUCT:
+ case STORAGE_TAGGED:
+ case STORAGE_TUPLE:
+ case STORAGE_UNION:
+ case STORAGE_VALIST:
+ case STORAGE_VOID:
+ case STORAGE_UNDEFINED:
+ assert(0);
+ }
+ assert(0);
+}
+
+static double
+ftrunc(struct context *ctx, const struct type *type, double val)
+{
+ if (type->storage == STORAGE_F32) {
+ return (float)val;
+ }
+ assert(type_is_float(ctx, type));
+ return val;
+}
+
+static bool
+eval_binarithm(struct context *ctx,
+ const struct expression *restrict in,
+ struct expression *restrict out)
+{
+ struct expression lvalue = {0}, rvalue = {0};
+ if (!eval_expr(ctx, in->binarithm.lvalue, &lvalue)) {
+ return false;
+ }
+ if (!eval_expr(ctx, in->binarithm.rvalue, &rvalue)) {
+ return false;
+ }
+
+ bool blval = false, brval = false, bval = false;
+ int64_t ilval = 0, irval = 0, ival = 0;
+ uint64_t ulval = 0, urval = 0, uval = 0;
+ double flval = 0, frval = 0, fval = 0;
+ if (type_is_float(ctx, lvalue.result)) {
+ flval = lvalue.literal.fval, frval = rvalue.literal.fval;
+ } else if (type_is_signed(ctx, lvalue.result)) {
+ ilval = lvalue.literal.ival, irval = rvalue.literal.ival;
+ } else if (type_is_integer(ctx, lvalue.result)) {
+ ulval = lvalue.literal.uval, urval = rvalue.literal.uval;
+ } else if (type_dealias(ctx, lvalue.result)->storage == STORAGE_BOOL) {
+ blval = lvalue.literal.bval, brval = rvalue.literal.bval;
+ }
+
+ // Type promotion is lowered in check
+ assert(lvalue.result->storage == rvalue.result->storage);
+ bool neg = false;
+ switch (in->binarithm.op) {
+ case BIN_BAND:
+ assert(type_is_integer(ctx, lvalue.result));
+ if (type_is_signed(ctx, lvalue.result)) {
+ ival = itrunc(ctx, lvalue.result, ilval) & itrunc(ctx, rvalue.result, irval);
+ } else {
+ uval = itrunc(ctx, lvalue.result, ulval) & itrunc(ctx, rvalue.result, urval);
+ }
+ break;
+ case BIN_BOR:
+ assert(type_is_integer(ctx, lvalue.result));
+ if (type_is_signed(ctx, lvalue.result)) {
+ ival = itrunc(ctx, lvalue.result, ilval) | itrunc(ctx, rvalue.result, irval);
+ } else {
+ uval = itrunc(ctx, lvalue.result, ulval) | itrunc(ctx, rvalue.result, urval);
+ }
+ break;
+ case BIN_DIV:
+ if (type_is_float(ctx, lvalue.result)) {
+ fval = ftrunc(ctx, lvalue.result, flval) / ftrunc(ctx, rvalue.result, frval);
+ } else if (type_is_signed(ctx, lvalue.result)) {
+ int64_t l = itrunc(ctx, lvalue.result, ilval);
+ int64_t r = itrunc(ctx, rvalue.result, irval);
+ if (r == 0) {
+ error(ctx, in->loc, NULL, "division by zero");
+ return false;
+ } else if (r == -1) {
+ uint64_t bit = lvalue.result->size * 8 - 1;
+ uint64_t min = -((uint64_t)1 << bit);
+ if (l == (int64_t)min) {
+ error(ctx, in->loc, NULL,
+ "division overflow");
+ return false;
+ }
+ }
+ ival = l / r;
+ } else {
+ assert(type_is_integer(ctx, lvalue.result));
+ uint64_t r = itrunc(ctx, rvalue.result, urval);
+ if (r == 0) {
+ error(ctx, in->loc, NULL, "division by zero");
+ return false;
+ }
+ uval = itrunc(ctx, lvalue.result, ulval) / r;
+ }
+ break;
+ case BIN_LSHIFT:
+ assert(type_is_integer(ctx, lvalue.result));
+ assert(type_is_integer(ctx, rvalue.result));
+ assert(!type_is_signed(ctx, rvalue.result));
+ uval = itrunc(ctx, lvalue.result, ulval) << itrunc(ctx, rvalue.result, urval);
+ break;
+ case BIN_MINUS:
+ if (type_is_float(ctx, lvalue.result)) {
+ fval = ftrunc(ctx, lvalue.result, flval) - ftrunc(ctx, rvalue.result, frval);
+ } else if (type_is_signed(ctx, lvalue.result)) {
+ ival = itrunc(ctx, lvalue.result, ilval) - itrunc(ctx, rvalue.result, irval);
+ } else {
+ assert(type_is_integer(ctx, lvalue.result));
+ uval = itrunc(ctx, lvalue.result, ulval) - itrunc(ctx, rvalue.result, urval);
+ }
+ break;
+ case BIN_MODULO:
+ assert(type_is_integer(ctx, lvalue.result));
+ if (type_is_signed(ctx, lvalue.result)) {
+ int64_t l = itrunc(ctx, lvalue.result, ilval);
+ int64_t r = itrunc(ctx, rvalue.result, irval);
+ if (r == 0) {
+ error(ctx, in->loc, NULL, "division by zero");
+ return false;
+ } else if (r == -1) {
+ uint64_t bit = lvalue.result->size * 8 - 1;
+ uint64_t min = -((uint64_t)1 << bit);
+ if (l == (int64_t)min) {
+ error(ctx, in->loc, NULL,
+ "division overflow");
+ return false;
+ }
+ }
+ ival = l % r;
+ } else {
+ uint64_t r = itrunc(ctx, rvalue.result, urval);
+ if (r == 0) {
+ error(ctx, in->loc, NULL, "division by zero");
+ return false;
+ }
+ uval = itrunc(ctx, lvalue.result, ulval) % r;
+ }
+ break;
+ case BIN_PLUS:
+ if (type_is_float(ctx, lvalue.result)) {
+ fval = ftrunc(ctx, lvalue.result, flval) + ftrunc(ctx, rvalue.result, frval);
+ } else if (type_is_signed(ctx, lvalue.result)) {
+ ival = itrunc(ctx, lvalue.result, ilval) + itrunc(ctx, rvalue.result, irval);
+ } else {
+ assert(type_is_integer(ctx, lvalue.result));
+ uval = itrunc(ctx, lvalue.result, ulval) + itrunc(ctx, rvalue.result, urval);
+ }
+ break;
+ case BIN_RSHIFT:
+ assert(type_is_integer(ctx, lvalue.result));
+ assert(type_is_integer(ctx, rvalue.result));
+ assert(!type_is_signed(ctx, rvalue.result));
+ uval = itrunc(ctx, lvalue.result, ulval) >> itrunc(ctx, rvalue.result, urval);
+ break;
+ case BIN_TIMES:
+ if (type_is_float(ctx, lvalue.result)) {
+ fval = ftrunc(ctx, lvalue.result, flval) * ftrunc(ctx, rvalue.result, frval);
+ } else if (type_is_signed(ctx, lvalue.result)) {
+ ival = (int64_t)itrunc(ctx, lvalue.result, ilval)
+ * (int64_t)itrunc(ctx, rvalue.result, irval);
+ } else {
+ assert(type_is_integer(ctx, lvalue.result));
+ uval = itrunc(ctx, lvalue.result, ulval) * itrunc(ctx, rvalue.result, urval);
+ }
+ break;
+ case BIN_BXOR:
+ assert(type_is_integer(ctx, lvalue.result));
+ if (type_is_signed(ctx, lvalue.result)) {
+ ival = itrunc(ctx, lvalue.result, ilval) ^ itrunc(ctx, rvalue.result, irval);
+ } else {
+ uval = itrunc(ctx, lvalue.result, ulval) ^ itrunc(ctx, rvalue.result, urval);
+ }
+ break;
+ // Logical arithmetic
+ case BIN_GREATER:
+ if (type_is_float(ctx, lvalue.result)) {
+ bval = ftrunc(ctx, lvalue.result, flval) > ftrunc(ctx, rvalue.result, frval);
+ } else if (type_is_signed(ctx, lvalue.result)) {
+ bval = (int64_t)itrunc(ctx, lvalue.result, ilval) > (int64_t)itrunc(ctx, rvalue.result, irval);
+ } else {
+ assert(type_is_integer(ctx, lvalue.result));
+ bval = itrunc(ctx, lvalue.result, ulval) > itrunc(ctx, rvalue.result, urval);
+ }
+ break;
+ case BIN_GREATEREQ:
+ if (type_is_float(ctx, lvalue.result)) {
+ bval = ftrunc(ctx, lvalue.result, flval) >= ftrunc(ctx, rvalue.result, frval);
+ } else if (type_is_signed(ctx, lvalue.result)) {
+ bval = (int64_t)itrunc(ctx, lvalue.result, ilval) >= (int64_t)itrunc(ctx, rvalue.result, irval);
+ } else {
+ assert(type_is_integer(ctx, lvalue.result));
+ bval = itrunc(ctx, lvalue.result, ulval) >= itrunc(ctx, rvalue.result, urval);
+ }
+ break;
+ case BIN_LAND:
+ assert(type_dealias(ctx, lvalue.result)->storage == STORAGE_BOOL
+ && type_dealias(ctx, rvalue.result)->storage == STORAGE_BOOL);
+ bval = blval && brval;
+ break;
+ case BIN_NEQUAL:
+ neg = true;
+ /* fallthrough */
+ case BIN_LEQUAL:
+ if (type_dealias(ctx, lvalue.result)->storage == STORAGE_POINTER) {
+ return false;
+ } else if (type_is_float(ctx, lvalue.result)) {
+ bval = ftrunc(ctx, lvalue.result, flval) == ftrunc(ctx, rvalue.result, frval);
+ } else if (type_is_signed(ctx, lvalue.result)) {
+ bval = itrunc(ctx, lvalue.result, ilval) == itrunc(ctx, rvalue.result, irval);
+ } else if (type_is_integer(ctx, lvalue.result)) {
+ bval = itrunc(ctx, lvalue.result, ulval) == itrunc(ctx, rvalue.result, urval);
+ } else if (type_dealias(ctx, lvalue.result)->storage == STORAGE_BOOL) {
+ bval = lvalue.literal.bval == rvalue.literal.bval;
+ } else if (type_dealias(ctx, lvalue.result)->storage == STORAGE_RCONST
+ || type_dealias(ctx, lvalue.result)->storage == STORAGE_RUNE) {
+ bval = lvalue.literal.rune == rvalue.literal.rune;
+ } else {
+ assert(type_dealias(ctx, lvalue.result)->storage == STORAGE_STRING);
+ if (lvalue.literal.string.len != rvalue.literal.string.len) {
+ bval = false;
+ } else {
+ bval = memcmp(lvalue.literal.string.value,
+ rvalue.literal.string.value,
+ lvalue.literal.string.len) == 0;
+ }
+ }
+ bval = bval != neg;
+ break;
+ case BIN_LESS:
+ if (type_is_float(ctx, lvalue.result)) {
+ bval = ftrunc(ctx, lvalue.result, flval) < ftrunc(ctx, rvalue.result, frval);
+ } else if (type_is_signed(ctx, lvalue.result)) {
+ bval = (int64_t)itrunc(ctx, lvalue.result, ilval) < (int64_t)itrunc(ctx, rvalue.result, irval);
+ } else {
+ assert(type_is_integer(ctx, lvalue.result));
+ bval = itrunc(ctx, lvalue.result, ulval) < itrunc(ctx, rvalue.result, urval);
+ }
+ break;
+ case BIN_LESSEQ:
+ if (type_is_float(ctx, lvalue.result)) {
+ bval = ftrunc(ctx, lvalue.result, flval) <= ftrunc(ctx, rvalue.result, frval);
+ } else if (type_is_signed(ctx, lvalue.result)) {
+ bval = (int64_t)itrunc(ctx, lvalue.result, ilval) <= (int64_t)itrunc(ctx, rvalue.result, irval);
+ } else {
+ assert(type_is_integer(ctx, lvalue.result));
+ bval = itrunc(ctx, lvalue.result, ulval) <= itrunc(ctx, rvalue.result, urval);
+ }
+ break;
+ case BIN_LOR:
+ assert(type_dealias(ctx, lvalue.result)->storage == STORAGE_BOOL
+ && type_dealias(ctx, rvalue.result)->storage == STORAGE_BOOL);
+ bval = blval || brval;
+ break;
+ case BIN_LXOR:
+ assert(type_dealias(ctx, lvalue.result)->storage == STORAGE_BOOL
+ && type_dealias(ctx, rvalue.result)->storage == STORAGE_BOOL);
+ bval = blval != brval;
+ break;
+ }
+ if (type_is_float(ctx, in->result)) {
+ out->literal.fval = ftrunc(ctx, in->result, fval);
+ } else if (type_is_signed(ctx, in->result)) {
+ out->literal.ival = itrunc(ctx, in->result, ival);
+ } else if (type_dealias(ctx, in->result)->storage == STORAGE_BOOL
+ || type_dealias(ctx, in->result)->storage == STORAGE_STRING) {
+ out->literal.bval = bval;
+ } else if (type_dealias(ctx, in->result)->storage == STORAGE_POINTER) {
+ return false;
+ } else {
+ assert(type_is_integer(ctx, in->result));
+ out->literal.uval = itrunc(ctx, in->result, uval);
+ }
+ return true;
+}
+
+static bool
+eval_literal(struct context *ctx,
+ const struct expression *restrict in,
+ struct expression *restrict out)
+{
+ enum type_storage storage = type_dealias(ctx, out->result)->storage;
+ if (storage == STORAGE_ENUM) {
+ storage = type_dealias(ctx, out->result)->alias.type->storage;
+ }
+ switch (storage) {
+ case STORAGE_ALIAS:
+ case STORAGE_ENUM:
+ case STORAGE_ERROR:
+ assert(0); // Handled above
+ case STORAGE_ARRAY:;
+ struct array_literal **anext = &out->literal.array;
+ for (struct array_literal *arr = in->literal.array; arr;
+ arr = arr->next) {
+ struct array_literal *alit = *anext =
+ xcalloc(1, sizeof(struct array_literal));
+ alit->value = xcalloc(1, sizeof(struct expression));
+ if (!eval_expr(ctx, arr->value, alit->value)) {
+ return false;
+ }
+ anext = &alit->next;
+ }
+ break;
+ case STORAGE_STRING:
+ out->literal.string.len = in->literal.string.len;
+ out->literal.string.value = xcalloc(1, in->literal.string.len);
+ memcpy(out->literal.string.value,
+ in->literal.string.value,
+ in->literal.string.len);
+ break;
+ case STORAGE_TAGGED:
+ out->literal.tagged.tag = in->literal.tagged.tag;
+ out->literal.tagged.value = xcalloc(sizeof(struct expression), 1);
+ return eval_expr(ctx, in->literal.tagged.value,
+ out->literal.tagged.value);
+ case STORAGE_STRUCT:;
+ struct struct_literal **next = &out->literal._struct;
+ for (struct struct_literal *_struct = in->literal._struct;
+ _struct; _struct = _struct->next) {
+ struct struct_literal *cur = *next =
+ xcalloc(sizeof(struct struct_literal), 1);
+ cur->field = _struct->field;
+ cur->value = xcalloc(sizeof(struct expression), 1);
+ if (!eval_expr(ctx, _struct->value, cur->value)) {
+ return false;
+ }
+ next = &cur->next;
+ }
+ break;
+ case STORAGE_UNION:
+ assert(0); // TODO
+ case STORAGE_TUPLE:;
+ struct tuple_literal **tnext = &out->literal.tuple;
+ for (struct tuple_literal *tuple = in->literal.tuple; tuple;
+ tuple = tuple->next) {
+ struct tuple_literal *tconst = *tnext =
+ xcalloc(1, sizeof(struct tuple_literal));
+ tconst->field = tuple->field;
+ tconst->value = xcalloc(1, sizeof(struct expression));
+ if (!eval_expr(ctx, tuple->value, tconst->value)) {
+ return false;
+ }
+ tnext = &tconst->next;
+ }
+ break;
+ case STORAGE_BOOL:
+ case STORAGE_DONE:
+ case STORAGE_INVALID:
+ case STORAGE_F64:
+ case STORAGE_FCONST:
+ case STORAGE_NOMEM:
+ case STORAGE_NULL:
+ case STORAGE_POINTER:
+ case STORAGE_SLICE:
+ case STORAGE_VOID:
+ out->literal = in->literal;
+ break;
+ case STORAGE_F32:
+ out->literal.fval = (float)in->literal.fval;
+ break;
+ case STORAGE_I16:
+ case STORAGE_I32:
+ case STORAGE_I64:
+ case STORAGE_I8:
+ case STORAGE_ICONST:
+ case STORAGE_INT:
+ case STORAGE_RCONST:
+ case STORAGE_RUNE:
+ case STORAGE_SIZE:
+ case STORAGE_U16:
+ case STORAGE_U32:
+ case STORAGE_U64:
+ case STORAGE_U8:
+ case STORAGE_UINT:
+ case STORAGE_UINTPTR:
+ out->literal.uval = itrunc(ctx, in->result, in->literal.uval);
+ break;
+ case STORAGE_FUNCTION:
+ case STORAGE_NEVER:
+ case STORAGE_OPAQUE:
+ case STORAGE_VALIST:
+ case STORAGE_UNDEFINED:
+ abort(); // Invariant
+ }
+ return true;
+}
+
+static void
+eval_expand_array(struct context *ctx,
+ const struct type *intype, const struct type *outtype,
+ const struct expression *restrict in, struct expression *restrict out)
+{
+ assert(in->type == EXPR_LITERAL);
+ assert(out->type == EXPR_LITERAL);
+ assert(intype->storage == STORAGE_ARRAY);
+ assert(outtype->storage == STORAGE_ARRAY);
+ struct array_literal *array_in = in->literal.array;
+ struct array_literal **next = &out->literal.array;
+ for (size_t i = 0; i < outtype->array.length; i++) {
+ struct array_literal *item = *next =
+ xcalloc(1, sizeof(struct array_literal));
+ item->value = array_in->value;
+ next = &item->next;
+ if (array_in->next) {
+ array_in = array_in->next;
+ }
+ }
+}
+
+static bool
+eval_type_assertion(struct context *ctx, const struct expression *restrict in,
+ struct expression *restrict out)
+{
+ struct expression val = {0};
+ if (!eval_expr(ctx, in->cast.value, &val)) {
+ return false;
+ }
+
+ const struct type *from = type_dealias(ctx, in->cast.value->result);
+ if (from->storage != STORAGE_TAGGED) {
+ return false;
+ }
+ if (val.literal.tagged.tag == in->cast.secondary) {
+ out->literal = val.literal.tagged.value->literal;
+ return true;
+ } else {
+ error(ctx, in->loc, NULL, "type assertion failed");
+ return false;
+ }
+}
+
+static bool
+eval_type_test(struct context *ctx, const struct expression *restrict in,
+ struct expression *restrict out)
+{
+ struct expression val = {0};
+ if (!eval_expr(ctx, in->cast.value, &val)) {
+ return false;
+ }
+
+ const struct type *from = type_dealias(ctx, in->cast.value->result);
+ if (from->storage != STORAGE_TAGGED) {
+ return false;
+ }
+
+ out->literal.bval = val.literal.tagged.tag == in->cast.secondary;
+
+ return true;
+}
+
+static bool
+eval_cast(struct context *ctx,
+ const struct expression *restrict in,
+ struct expression *restrict out)
+{
+ struct expression val = {0};
+ if (!eval_expr(ctx, in->cast.value, &val)) {
+ return false;
+ }
+
+ const struct type *to = type_dealias(ctx, in->result),
+ *from = type_dealias(ctx, val.result);
+ // The STORAGE_ARRAY exception is to make sure we handle expandable
+ // arrays at this point.
+ if (to->storage == from->storage && to->storage != STORAGE_ARRAY) {
+ out->literal = val.literal;
+ return true;
+ }
+
+ if (from->storage == STORAGE_INVALID) {
+ return true;
+ } else if (from->storage == STORAGE_UNDEFINED) {
+ out->type = EXPR_UNDEFINED;
+ out->result = to;
+ return true;
+ } else if (from->storage == STORAGE_TAGGED) {
+ out->literal = val.literal.tagged.value->literal;
+ return true;
+ }
+
+ // XXX: We should also be able to handle expressions which use
+ // symbols/identifiers
+
+ const struct type *subtype;
+ switch (to->storage) {
+ case STORAGE_POINTER:
+ if (from->storage == STORAGE_NULL) {
+ out->literal.uval = 0;
+ return true;
+ }
+ assert(from->storage == STORAGE_POINTER
+ || from->storage == STORAGE_UINTPTR);
+ out->literal.uval = val.literal.uval;
+ return true;
+ case STORAGE_ENUM:
+ case STORAGE_I16:
+ case STORAGE_I32:
+ case STORAGE_I64:
+ case STORAGE_I8:
+ case STORAGE_ICONST:
+ case STORAGE_INT:
+ case STORAGE_U16:
+ case STORAGE_U32:
+ case STORAGE_U64:
+ case STORAGE_U8:
+ case STORAGE_UINT:
+ case STORAGE_UINTPTR:
+ case STORAGE_SIZE:
+ case STORAGE_RCONST:
+ case STORAGE_RUNE:
+ if (type_dealias(ctx, from)->storage == STORAGE_POINTER) {
+ return false;
+ } else if (type_is_float(ctx, val.result)) {
+ out->literal.ival =
+ itrunc(ctx, to, (int64_t)val.literal.fval);
+ } else if (type_is_signed(ctx, val.result)) {
+ out->literal.ival = itrunc(ctx, to, val.literal.ival);
+ } else {
+ out->literal.ival = itrunc(ctx, to, val.literal.uval);
+ }
+ return true;
+ case STORAGE_ARRAY:
+ assert(from->storage == STORAGE_ARRAY);
+ if (from->array.expandable) {
+ eval_expand_array(ctx, from, to, &val, out);
+ } else {
+ out->literal = val.literal;
+ }
+ return true;
+ case STORAGE_SLICE:
+ assert(from->storage == STORAGE_ARRAY);
+ out->literal.slice.array = val.literal.array;
+ out->literal.slice.start = 0;
+ out->literal.slice.len = out->literal.slice.cap =
+ from->array.length;
+ return true;
+ case STORAGE_F32:
+ case STORAGE_F64:
+ case STORAGE_FCONST:
+ if (type_is_float(ctx, val.result)) {
+ out->literal.fval = ftrunc(ctx, to, val.literal.fval);
+ } else if (type_is_signed(ctx, val.result)) {
+ out->literal.fval =
+ ftrunc(ctx, to, (double)val.literal.ival);
+ } else {
+ out->literal.fval =
+ ftrunc(ctx, to, (double)val.literal.uval);
+ }
+ return true;
+ case STORAGE_TAGGED:
+ subtype = tagged_select_subtype(ctx, to, val.result, true);
+ out->literal.tagged.value =
+ xcalloc(1, sizeof(struct expression));
+ if (subtype) {
+ out->literal.tagged.tag = subtype;
+ *out->literal.tagged.value = val;
+ } else {
+ out->literal.tagged.tag = from;
+ *out->literal.tagged.value = val;
+ }
+ return true;
+ case STORAGE_NULL:
+ case STORAGE_ALIAS:
+ case STORAGE_ERROR:
+ assert(0); // Handled above
+ case STORAGE_BOOL:
+ case STORAGE_FUNCTION:
+ case STORAGE_NEVER:
+ case STORAGE_OPAQUE:
+ case STORAGE_STRING:
+ case STORAGE_STRUCT:
+ case STORAGE_TUPLE:
+ case STORAGE_UNION:
+ case STORAGE_VALIST:
+ assert(0); // Invariant
+ case STORAGE_DONE:
+ case STORAGE_INVALID:
+ case STORAGE_NOMEM:
+ case STORAGE_VOID:
+ case STORAGE_UNDEFINED:
+ return true;
+ }
+
+ assert(0); // Unreachable
+}
+
+static bool
+eval_len(struct context *ctx,
+ const struct expression *restrict in,
+ struct expression *restrict out)
+{
+ assert(in->type == EXPR_LEN);
+ const struct type *expr_type = type_dereference(ctx, in->len.value->result, false);
+ assert(expr_type != NULL);
+ expr_type = type_dealias(ctx, expr_type);
+
+ struct expression obj = {0};
+ if (!eval_expr(ctx, in->len.value, &obj)) {
+ return false;
+ }
+
+ switch (obj.result->storage) {
+ case STORAGE_SLICE:
+ out->literal.uval = obj.literal.slice.len;
+ return true;
+ case STORAGE_STRING:
+ out->literal.uval = obj.literal.string.len;
+ return true;
+ case STORAGE_INVALID:
+ out->literal.uval = 0;
+ return true;
+ case STORAGE_ARRAY:
+ default:
+ abort(); // Invariant
+ }
+
+ uint64_t len = 0;
+ for (struct array_literal *c = obj.literal.array;
+ c != NULL; c = c->next) {
+ len++;
+ }
+ out->literal.uval = len;
+ return true;
+}
+
+static bool
+literal_default(struct context *ctx, struct expression *v)
+{
+ struct expression b = {0};
+ const struct type *t = type_dealias(ctx, v->result);
+ switch (t->storage) {
+ case STORAGE_INVALID:
+ case STORAGE_POINTER:
+ case STORAGE_I16:
+ case STORAGE_I32:
+ case STORAGE_I64:
+ case STORAGE_I8:
+ case STORAGE_ICONST:
+ case STORAGE_INT:
+ case STORAGE_U16:
+ case STORAGE_U32:
+ case STORAGE_U64:
+ case STORAGE_U8:
+ case STORAGE_UINT:
+ case STORAGE_UINTPTR:
+ case STORAGE_SIZE:
+ case STORAGE_F32:
+ case STORAGE_F64:
+ case STORAGE_FCONST:
+ case STORAGE_ENUM:
+ case STORAGE_NULL:
+ case STORAGE_RCONST:
+ case STORAGE_RUNE:
+ case STORAGE_SLICE:
+ case STORAGE_BOOL:
+ case STORAGE_UNDEFINED:
+ break; // calloc does this for us
+ case STORAGE_STRUCT:
+ case STORAGE_UNION:
+ b.type = EXPR_STRUCT;
+ b.result = v->result;
+ b._struct.autofill = true;
+ bool r = eval_expr(ctx, &b, v);
+ assert(r);
+ break;
+ case STORAGE_STRING:
+ v->literal.string.value = NULL;
+ v->literal.string.len = 0;
+ break;
+ case STORAGE_ARRAY:
+ assert(!t->array.expandable); // Invariant
+ if (t->array.length == SIZE_UNDEFINED) {
+ return false;
+ }
+ struct array_literal **next = &v->literal.array;
+ for (size_t i = 0; i < t->array.length; i++) {
+ *next = xcalloc(1, sizeof(struct array_literal));
+ (*next)->value = xcalloc(1, sizeof(struct expression));
+ (*next)->value->type = EXPR_LITERAL;
+ (*next)->value->result = t->array.members;
+ if (!literal_default(ctx, (*next)->value)) {
+ return false;
+ }
+ next = &(*next)->next;
+ }
+ break;
+ case STORAGE_TAGGED:
+ return false;
+ case STORAGE_TUPLE:;
+ struct tuple_literal **c = &v->literal.tuple;
+ for (const struct type_tuple *t = &type_dealias(ctx, v->result)->tuple;
+ t != NULL; t = t->next) {
+ *c = xcalloc(1, sizeof(struct tuple_literal));
+ (*c)->field = t;
+ (*c)->value = xcalloc(1, sizeof(struct expression));
+ (*c)->value->type = EXPR_LITERAL;
+ (*c)->value->result = t->type;
+ if (!literal_default(ctx, (*c)->value)) {
+ return false;
+ }
+ c = &(*c)->next;
+ }
+ break;
+ case STORAGE_ALIAS:
+ case STORAGE_ERROR:
+ case STORAGE_FUNCTION:
+ case STORAGE_NEVER:
+ case STORAGE_OPAQUE:
+ case STORAGE_VALIST:
+ assert(0); // Invariant
+ case STORAGE_DONE:
+ case STORAGE_NOMEM:
+ case STORAGE_VOID:
+ break; // no-op
+ }
+
+ return true;
+}
+
+static int
+field_compar(const void *_a, const void *_b)
+{
+ const struct struct_literal **a = (const struct struct_literal **)_a;
+ const struct struct_literal **b = (const struct struct_literal **)_b;
+ return (*a)->field->offset - (*b)->field->offset;
+}
+
+static size_t
+count_struct_fields(struct context *ctx, const struct type *type)
+{
+ size_t n = 0;
+ assert(type->storage == STORAGE_STRUCT || type->storage == STORAGE_UNION);
+ for (const struct struct_field *field = type->struct_union.fields;
+ field; field = field->next) {
+ if (!field->name) {
+ n += count_struct_fields(ctx, type_dealias(ctx, field->type));
+ } else {
+ ++n;
+ }
+ }
+ return n;
+}
+
+static bool
+autofill_struct(
+ struct context *ctx,
+ const struct type *type,
+ struct struct_literal **fields,
+ bool undefined
+) {
+ assert(type->storage == STORAGE_STRUCT || type->storage == STORAGE_UNION);
+ for (const struct struct_field *field = type->struct_union.fields;
+ field; field = field->next) {
+ if (!field->name) {
+ bool r = autofill_struct(ctx,
+ type_dealias(ctx, field->type),
+ fields, undefined);
+ if (!r) {
+ return false;
+ }
+ continue;
+ }
+ size_t i = 0;
+ bool skip = false;
+ for (; fields[i]; ++i) {
+ if (!strcmp(field->name, fields[i]->field->name)) {
+ skip = true;
+ break;
+ }
+ }
+ if (!skip) {
+ fields[i] = xcalloc(1, sizeof(struct struct_literal));
+ fields[i]->field = field;
+ fields[i]->value = xcalloc(1, sizeof(struct expression));
+ fields[i]->value->result = field->type;
+ if (!literal_default(ctx, fields[i]->value)) {
+ // TODO: there should probably be a better
+ // error message when this happens
+ if (!undefined) {
+ return false;
+ }
+ fields[i]->value->type = EXPR_UNDEFINED;
+ } else {
+ fields[i]->value->type = EXPR_LITERAL;
+ }
+ }
+ }
+
+ return true;
+}
+
+static bool
+eval_struct(struct context *ctx,
+ const struct expression *restrict in,
+ struct expression *restrict out)
+{
+ assert(in->type == EXPR_STRUCT);
+ assert(type_dealias(ctx, in->result)->storage != STORAGE_UNION); // TODO
+ const struct type *type = type_dealias(ctx, in->result);
+
+ size_t n = count_struct_fields(ctx, type);
+ assert(n > 0);
+
+ size_t i = 0;
+ struct struct_literal **fields =
+ xcalloc(n, sizeof(struct struct_literal *));
+ for (const struct expr_struct_field *field_in = in->_struct.fields;
+ field_in; field_in = field_in->next, ++i) {
+ const struct struct_field *field =
+ type_get_field(ctx, type, field_in->field->name);
+ fields[i] = xcalloc(1, sizeof(struct struct_literal));
+ fields[i]->field = field;
+ fields[i]->value = xcalloc(1, sizeof(struct expression));
+
+ if (!eval_expr(ctx, field_in->value, fields[i]->value)) {
+ return false;
+ }
+ }
+ assert(in->_struct.autofill || i == n);
+
+ if (in->_struct.autofill) {
+ if (!autofill_struct(ctx, type, fields, in->_struct.undefined)) {
+ return false;
+ }
+ }
+
+ qsort(fields, n, sizeof(struct struct_literal *), field_compar);
+
+ for (size_t i = 0; i < n - 1; ++i) {
+ fields[i]->next = fields[i + 1];
+ }
+
+ out->literal._struct = fields[0];
+ free(fields);
+ return true;
+}
+
+static bool
+eval_slice(struct context *ctx,
+ const struct expression *restrict in,
+ struct expression *restrict out)
+{
+ assert(in->type == EXPR_SLICE);
+
+ const struct type *object_type = type_dealias(ctx, in->slice.object->result);
+
+ struct expression object = {0};
+ if (object_type->storage == STORAGE_SLICE) {
+ if (!eval_expr(ctx, in->slice.object, &object)) {
+ return false;
+ }
+ object_type = type_dealias(ctx, object.result);
+ } else if (object_type->storage == STORAGE_ARRAY) {
+ object = *in->slice.object;
+ } else {
+ return false;
+ }
+
+ size_t start = 0;
+ if (in->slice.start) {
+ struct expression start_expr = {0};
+ if (!eval_expr(ctx, in->slice.start, &start_expr)) {
+ return false;
+ }
+ start = start_expr.literal.uval;
+ }
+
+ size_t end;
+ if (object_type->storage == STORAGE_ARRAY) {
+ end = object_type->array.length;
+ } else {
+ end = object.literal.slice.len;
+ }
+ if (in->slice.end) {
+ struct expression end_expr = {0};
+ if (!eval_expr(ctx, in->slice.end, &end_expr)) {
+ return false;
+ }
+ end = end_expr.literal.uval;
+ }
+
+ if (object_type->storage == STORAGE_SLICE) {
+ if (start >= end || start >= object.literal.slice.len
+ || end > object.literal.slice.len) {
+ error(ctx, in->loc, NULL, "slice access out of bounds");
+ return false;
+ }
+
+ out->literal = object.literal;
+ out->literal.slice.start += start;
+ out->literal.slice.len = end - start;
+ out->literal.slice.cap -= start;
+ return true;
+ }
+
+ assert(object_type->storage == STORAGE_ARRAY);
+ out->literal.slice.start = start;
+ out->literal.slice.len = end - start;
+ out->literal.slice.cap = object_type->array.length - start;
+
+ switch (object.type) {
+ case EXPR_ACCESS:;
+ struct expression addr_expr = {0}, addr = {0};
+ addr_expr.type = EXPR_UNARITHM;
+ addr_expr.unarithm.op = UN_ADDRESS;
+ addr_expr.unarithm.operand = &object;
+
+ if (!eval_expr(ctx, &addr_expr, &addr)) {
+ return false;
+ }
+
+ out->literal.object = addr.literal.object;
+ out->literal.slice.offset = addr.literal.ival;
+ break;
+ case EXPR_LITERAL:
+ out->literal.object = NULL;
+ out->literal.slice.array = object.literal.array;
+ break;
+ default:
+ assert(0); // Invariant
+ }
+ return true;
+}
+
+static bool
+eval_tuple(struct context *ctx,
+ const struct expression *restrict in,
+ struct expression *restrict out)
+{
+ assert(in->type == EXPR_TUPLE);
+ const struct type *type = type_dealias(ctx, in->result);
+
+ struct tuple_literal *out_tuple_start, *out_tuple;
+ out_tuple_start = out_tuple = xcalloc(1, sizeof(struct tuple_literal));
+ const struct expression_tuple *in_tuple = &in->tuple;
+ for (const struct type_tuple *field_type = &type->tuple; field_type;
+ field_type = field_type->next) {
+ out_tuple->value = xcalloc(1, sizeof(struct expression));
+ if (!eval_expr(ctx, in_tuple->value, out_tuple->value)) {
+ return false;
+ }
+ out_tuple->field = field_type;
+ if (in_tuple->next) {
+ in_tuple = in_tuple->next;
+ out_tuple->next =
+ xcalloc(1, sizeof(struct tuple_literal));
+ out_tuple = out_tuple->next;
+ }
+ }
+
+ out->literal.tuple = out_tuple_start;
+ return true;
+}
+
+static bool
+eval_address_object(struct context *ctx,
+ const struct expression *restrict in,
+ struct expression *restrict out)
+{
+ const struct expression_access *access =
+ &in->unarithm.operand->access;
+ struct expression new_in = {0};
+ const struct type *operand_type;
+ switch (access->type) {
+ case ACCESS_IDENTIFIER:
+ if (access->object->otype != O_DECL) {
+ return false;
+ }
+ out->literal.object = access->object;
+ out->literal.ival = 0;
+ return true;
+ case ACCESS_INDEX:
+ new_in = *in;
+ new_in.unarithm.operand = access->array;
+ if (!eval_expr(ctx, &new_in, out)) {
+ return false;
+ }
+ struct expression index = {0};
+ if (!eval_expr(ctx, access->index, &index)) {
+ return false;
+ }
+ operand_type = type_dealias(ctx, access->array->result);
+ if (operand_type->storage != STORAGE_ARRAY) {
+ // autodereferencing not allowed
+ return false;
+ }
+ out->literal.ival +=
+ index.literal.uval * operand_type->array.members->size;
+ return true;
+ case ACCESS_FIELD:
+ new_in = *in;
+ new_in.unarithm.operand = access->_struct;
+ if (!eval_expr(ctx, &new_in, out)) {
+ return false;
+ }
+ operand_type = type_dealias(ctx, access->tuple->result);
+ if (operand_type->storage != STORAGE_STRUCT) {
+ // autodereferencing not allowed
+ return false;
+ }
+ out->literal.ival += access->field->offset;
+ return true;
+ case ACCESS_TUPLE:
+ new_in = *in;
+ new_in.unarithm.operand = access->tuple;
+ if (!eval_expr(ctx, &new_in, out)) {
+ return false;
+ }
+ operand_type = type_dealias(ctx, access->tuple->result);
+ if (operand_type->storage != STORAGE_TUPLE) {
+ // autodereferencing not allowed
+ return false;
+ }
+ out->literal.ival += access->tvalue->offset;
+ return true;
+ }
+ return true;
+}
+
+static bool
+eval_address_other(struct context *ctx,
+ const struct expression *restrict in,
+ struct expression *restrict out)
+{
+ struct expression *value = xcalloc(1, sizeof(struct expression));
+ if (!eval_expr(ctx, in->unarithm.operand, value)) {
+ return false;
+ }
+
+ char *symbol = gen_name(&ctx->id, "static.%d");
+ struct ident *name = mkident(ctx, NULL, symbol);
+
+ append_decl(ctx, &(struct declaration){
+ .decl_type = DECL_GLOBAL,
+ .file = in->loc.file,
+ .ident = name,
+ .symbol = symbol,
+ .exported = false,
+ .global = {
+ .type = value->result,
+ .value = value,
+ .threadlocal = false,
+ }
+ });
+
+ struct scope_object *obj = scope_insert(ctx->scope,
+ O_DECL, name, name, value->result, NULL);
+
+ struct expression shadow = *in;
+ shadow.unarithm.operand = &(struct expression){
+ .type = EXPR_ACCESS,
+ .access = (struct expression_access){
+ .type = ACCESS_IDENTIFIER,
+ .object = obj,
+ },
+ };
+ bool r = eval_address_object(ctx, &shadow, out);
+ assert(r);
+ return true;
+}
+
+static bool
+eval_unarithm(struct context *ctx,
+ const struct expression *restrict in,
+ struct expression *restrict out)
+{
+ if (in->unarithm.op == UN_ADDRESS) {
+ if (in->unarithm.operand->result == &builtin_type_invalid) {
+ out->type = EXPR_LITERAL;
+ out->result = &builtin_type_invalid;
+ out->literal.uval = 0;
+ return true;
+ }
+ switch (in->unarithm.operand->type) {
+ case EXPR_ACCESS:
+ return eval_address_object(ctx, in, out);
+ default:
+ return eval_address_other(ctx, in, out);
+ }
+ }
+
+ struct expression lvalue = {0};
+ if (!eval_expr(ctx, in->unarithm.operand, &lvalue)) {
+ return false;
+ }
+
+ switch (in->unarithm.op) {
+ case UN_ADDRESS:
+ assert(0); // handled above
+ case UN_BNOT:
+ out->literal.uval = itrunc(ctx, out->result, ~lvalue.literal.uval);
+ break;
+ case UN_DEREF:
+ return false;
+ case UN_LNOT:
+ out->literal.bval = !lvalue.literal.bval;
+ break;
+ case UN_MINUS:
+ if (type_is_float(ctx, out->result)) {
+ out->literal.fval = -lvalue.literal.fval;
+ } else {
+ out->literal.ival = itrunc(ctx, out->result,
+ -(uint64_t)lvalue.literal.ival);
+ }
+ break;
+ }
+
+ return true;
+}
+
+bool
+eval_expr(struct context *ctx,
+ const struct expression *restrict in,
+ struct expression *restrict out)
+{
+ out->loc = in->loc;
+ out->result = in->result;
+ out->type = EXPR_LITERAL;
+
+ switch (in->type) {
+ case EXPR_ACCESS:
+ return eval_access(ctx, in, out);
+ case EXPR_BINARITHM:
+ return eval_binarithm(ctx, in, out);
+ case EXPR_CAST:
+ switch (in->cast.kind) {
+ case C_CAST:
+ return eval_cast(ctx, in, out);
+ case C_ASSERTION:
+ return eval_type_assertion(ctx, in, out);
+ case C_TEST:
+ return eval_type_test(ctx, in, out);
+ default:
+ assert(0); // Unreachable
+ }
+ case EXPR_LEN:
+ return eval_len(ctx, in, out);
+ case EXPR_LITERAL:
+ return eval_literal(ctx, in, out);
+ case EXPR_STRUCT:
+ return eval_struct(ctx, in, out);
+ case EXPR_SLICE:
+ return eval_slice(ctx, in, out);
+ case EXPR_TUPLE:
+ return eval_tuple(ctx, in, out);
+ case EXPR_UNARITHM:
+ return eval_unarithm(ctx, in, out);
+ case EXPR_UNDEFINED:
+ out->type = EXPR_UNDEFINED;
+ out->result = &builtin_type_undefined;
+ return true;
+ case EXPR_ALLOC:
+ case EXPR_APPEND:
+ case EXPR_ASSERT:
+ case EXPR_ASSIGN:
+ case EXPR_BINDING:
+ case EXPR_BREAK:
+ case EXPR_CALL:
+ case EXPR_COMPOUND:
+ case EXPR_CONTINUE:
+ case EXPR_DEFER:
+ case EXPR_DEFINE:
+ case EXPR_DELETE:
+ case EXPR_FOR:
+ case EXPR_FREE:
+ case EXPR_IF:
+ case EXPR_INSERT:
+ case EXPR_MATCH:
+ case EXPR_PROPAGATE:
+ case EXPR_RETURN:
+ case EXPR_SWITCH:
+ case EXPR_VAARG:
+ case EXPR_VAEND:
+ case EXPR_VASTART:
+ case EXPR_YIELD:
+ return false;
+ }
+ assert(0); // Unreachable
+}
diff --git a/ref/harec/src/scope.c b/ref/harec/src/scope.c
new file mode 100644
index 00000000..5b6cfadb
--- /dev/null
+++ b/ref/harec/src/scope.c
@@ -0,0 +1,136 @@
+#include
+#include
+#include
+#include "expr.h"
+#include "identifier.h"
+#include "scope.h"
+#include "util.h"
+
+static uint32_t
+name_hash(uint32_t init, const struct ident *ident)
+{
+ return fnv1a_s(init, ident->name);
+}
+
+struct scope *
+scope_push(struct scope **stack, enum scope_class class)
+{
+ struct scope *new = xcalloc(1, sizeof(struct scope));
+ new->class = class;
+ new->results.types = NULL;
+ new->next = &new->objects;
+ new->parent = *stack;
+ *stack = new;
+ return new;
+}
+
+struct scope *
+scope_pop(struct scope **stack)
+{
+ struct scope *prev = *stack;
+ assert(prev);
+ *stack = prev->parent;
+ return prev;
+}
+
+struct scope *
+scope_lookup_class(struct scope *scope, enum scope_class class)
+{
+ while (scope) {
+ if (scope->class == class) {
+ break;
+ }
+ scope = scope->parent;
+ }
+ return scope;
+}
+
+struct scope *
+scope_lookup_label(struct scope *scope, const char *label)
+{
+ while (scope) {
+ if (scope->label && strcmp(scope->label, label) == 0) {
+ break;
+ }
+ scope = scope->parent;
+ }
+ return scope;
+}
+
+void
+scope_free(struct scope *scope)
+{
+ if (!scope) {
+ return;
+ }
+
+ struct scope_object *obj = scope->objects;
+ while (obj) {
+ struct scope_object *next = obj->lnext;
+ free(obj);
+ obj = next;
+ }
+
+ free(scope);
+}
+
+void
+scope_free_all(struct scopes *scopes)
+{
+ while (scopes) {
+ struct scopes *next = scopes->next;
+ scope_free(scopes->scope);
+ free(scopes);
+ scopes = next;
+ }
+}
+
+struct scope_object *
+scope_insert(struct scope *scope, enum object_type otype,
+ struct ident *ident, struct ident *name, const struct type *type,
+ struct expression *value)
+{
+ assert(otype == O_SCAN || !type != !value);
+ struct scope_object *obj = xcalloc(1, sizeof(struct scope_object));
+ obj->ident = ident;
+ obj->name = name;
+ obj->otype = otype;
+ if (type) {
+ obj->type = type;
+ } else if (value) {
+ obj->value = value;
+ assert(otype == O_CONST);
+ assert(value->type == EXPR_LITERAL);
+ }
+ flexible_refer(type, &obj->type);
+
+ // Linked list
+ *scope->next = obj;
+ scope->next = &obj->lnext;
+
+ // Hash map
+ uint32_t hash = name_hash(FNV1A_INIT, obj->name);
+ struct scope_object **bucket = &scope->buckets[hash % SCOPE_BUCKETS];
+ if (*bucket) {
+ obj->mnext = *bucket;
+ }
+ *bucket = obj;
+ return obj;
+}
+
+struct scope_object *
+scope_lookup(struct scope *scope, struct ident *ident)
+{
+ uint32_t hash = name_hash(FNV1A_INIT, ident);
+ struct scope_object *bucket = scope->buckets[hash % SCOPE_BUCKETS];
+ while (bucket) {
+ if (bucket->name == ident) {
+ return bucket;
+ }
+ bucket = bucket->mnext;
+ }
+ if (scope->parent) {
+ return scope_lookup(scope->parent, ident);
+ }
+ return NULL;
+}
diff --git a/ref/harec/src/type_store.c b/ref/harec/src/type_store.c
new file mode 100644
index 00000000..f44f44b4
--- /dev/null
+++ b/ref/harec/src/type_store.c
@@ -0,0 +1,1296 @@
+#include
+#include
+#include
+#include
+#include "check.h"
+#include "eval.h"
+#include "identifier.h"
+#include "scope.h"
+#include "type_store.h"
+#include "types.h"
+#include "util.h"
+
+static struct dimensions
+dim_from_type(const struct type *type)
+{
+ return (struct dimensions){ .size = type->size, .align = type->align };
+}
+
+static size_t
+ast_array_len(struct context *ctx, const struct ast_type *atype)
+{
+ // TODO: Maybe we should cache these
+ struct expression in, out;
+ if (atype->array.length == NULL) {
+ return SIZE_UNDEFINED;
+ }
+ check_expression(ctx, atype->array.length, &in, NULL);
+ if (!eval_expr(ctx, &in, &out)) {
+ error(ctx, atype->loc, NULL,
+ "Cannot evaluate array length at compile time");
+ return SIZE_UNDEFINED;
+ }
+ if (!type_is_integer(ctx, out.result)) {
+ error(ctx, atype->loc, NULL, "Array length must be an integer");
+ return SIZE_UNDEFINED;
+ }
+ if (type_is_signed(ctx, out.result) && out.literal.ival < 0) {
+ error(ctx, atype->loc, NULL,
+ "Array length must be non-negative");
+ return SIZE_UNDEFINED;
+ }
+ return (size_t)out.literal.uval;
+}
+
+const struct type *
+builtin_type_for_storage(enum type_storage storage)
+{
+ switch (storage) {
+ case STORAGE_BOOL:
+ return &builtin_type_bool;
+ case STORAGE_INVALID:
+ return &builtin_type_invalid;
+ case STORAGE_F32:
+ return &builtin_type_f32;
+ case STORAGE_F64:
+ return &builtin_type_f64;
+ case STORAGE_I8:
+ return &builtin_type_i8;
+ case STORAGE_I16:
+ return &builtin_type_i16;
+ case STORAGE_I32:
+ return &builtin_type_i32;
+ case STORAGE_I64:
+ return &builtin_type_i64;
+ case STORAGE_INT:
+ return &builtin_type_int;
+ case STORAGE_NEVER:
+ return &builtin_type_never;
+ case STORAGE_NOMEM:
+ return &builtin_type_nomem;
+ case STORAGE_OPAQUE:
+ return &builtin_type_opaque;
+ case STORAGE_RUNE:
+ return &builtin_type_rune;
+ case STORAGE_SIZE:
+ return &builtin_type_size;
+ case STORAGE_U8:
+ return &builtin_type_u8;
+ case STORAGE_U16:
+ return &builtin_type_u16;
+ case STORAGE_U32:
+ return &builtin_type_u32;
+ case STORAGE_U64:
+ return &builtin_type_u64;
+ case STORAGE_UINT:
+ return &builtin_type_uint;
+ case STORAGE_UINTPTR:
+ return &builtin_type_uintptr;
+ case STORAGE_VALIST:
+ return &builtin_type_valist;
+ case STORAGE_VOID:
+ return &builtin_type_void;
+ case STORAGE_DONE:
+ return &builtin_type_done;
+ case STORAGE_NULL:
+ return &builtin_type_null;
+ case STORAGE_STRING:
+ return &builtin_type_str;
+ case STORAGE_UNDEFINED:
+ return &builtin_type_undefined;
+ case STORAGE_ALIAS:
+ case STORAGE_ARRAY:
+ case STORAGE_ERROR:
+ case STORAGE_FUNCTION:
+ case STORAGE_FCONST:
+ case STORAGE_ICONST:
+ case STORAGE_RCONST:
+ case STORAGE_POINTER:
+ case STORAGE_SLICE:
+ case STORAGE_STRUCT:
+ case STORAGE_TAGGED:
+ case STORAGE_TUPLE:
+ case STORAGE_UNION:
+ case STORAGE_ENUM:
+ return NULL;
+ }
+ assert(0); // Unreachable
+}
+
+static const struct type *
+builtin_for_type(const struct type *type)
+{
+ return builtin_type_for_storage(type->storage);
+}
+
+static bool
+struct_union_has_field(struct context *ctx,
+ const char *name,
+ const struct struct_field *fields)
+{
+ for (; fields; fields = fields->next) {
+ if (fields->name != NULL) {
+ if (strcmp(fields->name, name) == 0) {
+ return true;
+ }
+ continue;
+ }
+
+ assert(fields->type != NULL);
+ const struct type *type = type_dealias(ctx, fields->type);
+ if (struct_union_has_field(ctx, name, type->struct_union.fields)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void
+add_padding(size_t *size, size_t align)
+{
+ if (*size != SIZE_UNDEFINED && *size != 0 && *size % align != 0) {
+ *size += align - *size % align;
+ }
+}
+
+static struct struct_field *
+struct_new_field(struct context *ctx, struct type *type,
+ const struct ast_struct_union_field *afield, bool size_only)
+{
+ bool named = afield->name != NULL && strcmp(afield->name, "_") != 0;
+ if (named && !size_only) {
+ if (struct_union_has_field(ctx, afield->name, type->struct_union.fields)) {
+ error(ctx, afield->type->loc, NULL,
+ "Duplicate struct/union member '%s'",
+ afield->name);
+ return NULL;
+ }
+ }
+ struct struct_field *field = xcalloc(1, sizeof(struct struct_field));
+
+ if (afield->name && !size_only) {
+ field->name = afield->name;
+ }
+ struct dimensions dim = {0};
+ if (size_only) {
+ dim = type_store_lookup_dimensions(ctx, afield->type);
+ } else {
+ field->type = type_store_lookup_atype(ctx, afield->type);
+ dim = dim_from_type(field->type);
+ }
+ if (afield->next != NULL && dim.size == SIZE_UNDEFINED) {
+ error(ctx, afield->type->loc, NULL,
+ "Type of undefined size is not a valid struct/union member");
+ return NULL;
+ }
+ if (dim.align == ALIGN_UNDEFINED) {
+ error(ctx, afield->type->loc, NULL,
+ "Type of undefined alignment is not a valid struct/union member");
+ return NULL;
+ }
+
+ type->align = dim.align > type->align ? dim.align : type->align;
+ field->size = dim.size;
+
+ if (type->storage == STORAGE_UNION) {
+ field->offset = 0;
+ if (dim.size == SIZE_UNDEFINED || type->size == SIZE_UNDEFINED) {
+ type->size = SIZE_UNDEFINED;
+ } else {
+ type->size = dim.size > type->size ? dim.size : type->size;
+ }
+ return field;
+ }
+
+ if (type->struct_union.packed) {
+ field->offset = type->size;
+ } else {
+ field->offset = type->size;
+ if (dim.align != 0) {
+ add_padding(&field->offset, dim.align);
+ }
+ }
+
+ if (dim.size == SIZE_UNDEFINED || type->size == SIZE_UNDEFINED) {
+ type->size = SIZE_UNDEFINED;
+ } else {
+ type->size = field->offset + dim.size;
+ }
+ return field;
+}
+
+static const struct type *type_store_lookup_type(struct context *ctx,
+ const struct type *type);
+
+static bool
+check_embedded_member(struct context *ctx,
+ const struct ast_struct_union_field *afield,
+ struct struct_field *member,
+ const struct struct_field *fields)
+{
+ assert(member->type != NULL);
+ const struct type *dealiased = type_dealias(ctx, member->type);
+ if (dealiased->storage != STORAGE_STRUCT
+ && dealiased->storage != STORAGE_UNION) {
+ error(ctx, afield->type->loc, NULL,
+ "Cannot embed non-struct non-union alias");
+ member->type = &builtin_type_invalid;
+ return false;
+ }
+
+ for (struct struct_field *field = dealiased->struct_union.fields;
+ field; field = field->next) {
+ if (field->name != NULL) {
+ if (strcmp(field->name, "_") == 0) {
+ continue;
+ }
+ if (struct_union_has_field(ctx, field->name, fields)) {
+ // XXX: the location could be better
+ error(ctx, afield->type->loc, NULL,
+ "Duplicate struct/union member '%s'",
+ field->name);
+ return false;
+ }
+ } else {
+ if (!check_embedded_member(ctx, afield, field, fields)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+static void
+shift_fields(struct context *ctx,
+ const struct ast_struct_union_field *afield, struct struct_field *parent)
+{
+ if (parent->offset == 0) {
+ // We need to return early here in order to avoid dealiasing an
+ // embedded alias. This is acceptable at nonzero offsets, but we
+ // need to keep the alias if it's at offset 0 because of
+ // subtyping.
+ return;
+ }
+ const struct type *type = type_dealias(ctx, parent->type);
+ assert(type->storage == STORAGE_STRUCT
+ || type->storage == STORAGE_UNION);
+ struct type new = {
+ .storage = type->storage,
+ .size = type->size,
+ .align = type->align,
+ .struct_union.packed = type->struct_union.packed,
+ };
+ struct struct_field **next = &new.struct_union.fields;
+ for (struct struct_field *field = type->struct_union.fields; field;
+ field = field->next) {
+ struct struct_field *new = *next =
+ xcalloc(1, sizeof(struct struct_field));
+ next = &new->next;
+ new->type = field->type;
+ new->offset = parent->offset;
+ if (field->name) {
+ new->name = field->name;
+ } else {
+ shift_fields(ctx, NULL, new);
+ }
+ // Sub-subfields are shifted by field->offset in the recursive
+ // shift_fields call, delay adding it to new->offset to avoid
+ // shifting by field->offset twice
+ new->offset += field->offset;
+ }
+
+ parent->type = type_store_lookup_type(ctx, &new);
+}
+
+static bool
+struct_init_from_atype(struct context *ctx, struct type *type,
+ const struct ast_type *atype, bool size_only)
+{
+ // TODO: fields with size SIZE_UNDEFINED
+ type->struct_union.packed = atype->struct_union.packed;
+
+ assert(type->storage == STORAGE_STRUCT || type->storage == STORAGE_UNION);
+ struct struct_field **next = &type->struct_union.fields;
+ for (const struct ast_struct_union_field *afield = &atype->struct_union.fields;
+ afield; afield = afield->next) {
+ size_t offset = type->size;
+ struct struct_field *field =
+ struct_new_field(ctx, type, afield, size_only);
+ if (field == NULL) {
+ return false;
+ }
+ if (type->size < offset) {
+ error(ctx, atype->loc, NULL, "Type is too big");
+ return false;
+ }
+ if (size_only) {
+ free(field);
+ continue;
+ } else if (!field->name) {
+ if (!check_embedded_member(ctx, afield, field,
+ type->struct_union.fields)) {
+ return false;
+ }
+ // We need to shift the embedded struct/union's fields
+ // so that their offsets are from the start of the
+ // parent type. This is a bit of a hack, but it makes
+ // type_get_field far easier to implement and doesn't
+ // cause any trouble in gen since offsets are only used
+ // there for sorting fields.
+ shift_fields(ctx, afield, field);
+ }
+ *next = field;
+ next = &field->next;
+ }
+ return true;
+}
+
+static void
+size_with_tag(struct dimensions *out, struct dimensions new)
+{
+ if (new.size == SIZE_UNDEFINED || out->size == SIZE_UNDEFINED) {
+ out->size = SIZE_UNDEFINED;
+ out->align = ALIGN_UNDEFINED;
+ return;
+ }
+ assert(new.align != ALIGN_UNDEFINED && out->align != ALIGN_UNDEFINED);
+
+ size_t sz = new.size + builtin_type_u32.size;
+ size_t align = new.align;
+ if (align < builtin_type_u32.align) {
+ align = builtin_type_u32.align;
+ }
+ add_padding(&sz, align);
+
+ if (sz > out->size) {
+ out->size = sz;
+ }
+ if (align > out->align) {
+ out->align = align;
+ }
+}
+
+static struct dimensions
+tagged_size(struct context *ctx, const struct ast_tagged_union_type *atype)
+{
+ struct dimensions ret = { 0 };
+ assert(atype != NULL);
+ for (; atype; atype = atype->next) {
+ if (!atype->unwrap) {
+ size_with_tag(&ret,
+ type_store_lookup_dimensions(ctx, atype->type));
+ continue;
+ }
+
+ const struct type *unwrapped =
+ type_store_lookup_atype(ctx, atype->type);
+ unwrapped = type_dealias(ctx, unwrapped);
+ if (unwrapped->storage != STORAGE_TAGGED) {
+ if (unwrapped->storage != STORAGE_INVALID) {
+ char *typename = gen_typename(unwrapped);
+ error(ctx, atype->type->loc, NULL,
+ "Can't reduce non-tagged-union type %s",
+ typename);
+ free(typename);
+ }
+ return ret;
+ }
+
+ for (size_t i = 0; i < unwrapped->tagged.len; i++) {
+ const struct type *mtype = unwrapped->tagged.types[i];
+ size_with_tag(&ret, dim_from_type(mtype));
+ }
+ }
+ return ret;
+}
+
+static int
+tagged_cmp(const void *_a, const void *_b)
+{
+ const struct type *a = *(const struct type **)_a;
+ const struct type *b = *(const struct type **)_b;
+ return a->id < b->id ? -1 : a->id > b->id ? 1 : 0;
+}
+
+static void
+tagged_init(struct context *ctx, struct type *type,
+ struct location loc, bool valid)
+{
+ const struct type **membs = type->tagged.types;
+
+ // Lower flexible constants
+ // TODO: Don't do this if !valid, and handle flexible literals properly
+ // in result type reduction
+ for (size_t i = 0; i < type->tagged.len; i++) {
+ membs[i] = lower_flexible(ctx, membs[i], NULL);
+ }
+
+ // Then sort by ID
+ qsort(membs, type->tagged.len, sizeof(membs[0]), tagged_cmp);
+
+ // Then deduplicate and enforce validity
+ size_t dedup_len = 1;
+ bool invalid = false;
+ for (size_t i = 1; i < type->tagged.len; i++) {
+ if (membs[i]->id != membs[i - 1]->id) {
+ membs[dedup_len++] = membs[i];
+ } else if (!type_equal(membs[i], membs[i - 1])) {
+ char *first_name = gen_typename(membs[i - 1]);
+ char *second_name = gen_typename(membs[i]);
+ error(ctx, loc, NULL,
+ "Tagged union can't contain both %s and %s (hash collision)",
+ first_name, second_name);
+ free(first_name);
+ free(second_name);
+ }
+ assert(membs[i]->storage != STORAGE_NULL || !valid);
+ if (membs[i]->size == SIZE_UNDEFINED && valid) {
+ error(ctx, loc, NULL,
+ "Type of undefined size is not a valid tagged union member");
+ invalid = true;
+ }
+ assert(membs[i]->align != ALIGN_UNDEFINED || invalid || !valid);
+ }
+ if (dedup_len < type->tagged.len) {
+ type->tagged.len = dedup_len;
+ }
+
+ if (invalid) {
+ *type = builtin_type_invalid;
+ return;
+ }
+ if (type->tagged.len == 0) {
+ return;
+ }
+
+ struct dimensions dims = {0};
+ size_t maxsize = 0;
+ for (size_t i = 0; i < type->tagged.len; i++) {
+ if (membs[i]->size != SIZE_UNDEFINED && membs[i]->size > maxsize) {
+ maxsize = membs[i]->size;
+ }
+ size_with_tag(&dims, dim_from_type(membs[i]));
+ }
+
+ type->size = dims.size;
+ type->align = dims.align;
+ add_padding(&type->size, type->align);
+ if (dims.size <= maxsize || type->size < dims.size) {
+ error(ctx, loc, NULL, "Type is too big");
+ *type = builtin_type_invalid;
+ }
+}
+
+static void
+tagged_init_from_atype(struct context *ctx,
+ struct type *type, const struct ast_type *atype)
+{
+ assert(atype->storage == STORAGE_TAGGED);
+ const struct ast_tagged_union_type *amemb = &atype->tagged;
+ type->tagged.types = NULL;
+ type->tagged.len = 0;
+ type->tagged.cap = 0;
+ for (; amemb; amemb = amemb->next) {
+ const struct type *memb =
+ type_store_lookup_atype(ctx, amemb->type);
+ if (!amemb->unwrap) {
+ tagged_append(&type->tagged, memb);
+ continue;
+ }
+
+ memb = type_dealias(ctx, memb);
+ if (memb->storage != STORAGE_TAGGED) {
+ if (memb->storage != STORAGE_INVALID) {
+ char *typename = gen_typename(memb);
+ error(ctx, atype->loc, NULL,
+ "Can't reduce non-tagged-union type %s",
+ typename);
+ free(typename);
+ }
+ *type = builtin_type_invalid;
+ return;
+ }
+ assert(memb->storage == STORAGE_TAGGED);
+ for (size_t i = 0; i < memb->tagged.len; i++) {
+ tagged_append(&type->tagged, memb->tagged.types[i]);
+ }
+ }
+ tagged_init(ctx, type, atype->loc, true);
+ if (type->storage == STORAGE_INVALID) {
+ return;
+ }
+
+ if (type->tagged.len <= 1) {
+ error(ctx, atype->loc, NULL,
+ "Tagged unions must have at least two distinct members");
+ *type = builtin_type_invalid;
+ }
+}
+
+static struct dimensions
+tuple_init_from_atype(struct context *ctx,
+ struct type *type, const struct ast_type *atype)
+{
+ const struct ast_tuple_type *atuple = &atype->tuple;
+ struct type_tuple *cur = NULL;
+ if (type) {
+ type->size = 0, type->align = 0;
+ cur = &type->tuple;
+ }
+ struct dimensions dim = {0};
+ while (atuple) {
+ struct dimensions memb = {0};
+ if (type) {
+ cur->type = type_store_lookup_atype(ctx, atuple->type);
+ memb = dim_from_type(cur->type);
+ } else {
+ memb = type_store_lookup_dimensions(ctx, atuple->type);
+ }
+ if (memb.size == SIZE_UNDEFINED) {
+ error(ctx, atype->loc, NULL,
+ "Type of undefined size is not a valid tuple member");
+ if (type) {
+ *type = builtin_type_invalid;
+ }
+ return (struct dimensions){0};
+ }
+ size_t offset = dim.size;
+ if (memb.align != 0) {
+ add_padding(&offset, memb.align);
+ }
+ if (offset < dim.size || offset + memb.size < offset) {
+ error(ctx, atype->loc, NULL, "Type is too big");
+ if (type) {
+ *type = builtin_type_invalid;
+ }
+ return (struct dimensions){0};
+ }
+ dim.size = offset + memb.size;
+ if (dim.align < memb.align) {
+ dim.align = memb.align;
+ }
+
+ atuple = atuple->next;
+ if (type) {
+ cur->offset = offset;
+ if (atuple) {
+ cur->next = xcalloc(1, sizeof(struct type_tuple));
+ cur = cur->next;
+ }
+ }
+ }
+ if (type) {
+ type->size = dim.size;
+ type->align = dim.align;
+ }
+ return dim;
+}
+
+static bool
+default_param_from_atype(struct context *ctx,
+ const struct ast_function_parameters *aparam,
+ struct type_func_param *param)
+{
+ // This is leaked. check_expression makes a flexible ref that may be
+ // updated later, so it cannot be on the stack.
+ struct expression *in = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aparam->default_value, in, param->type);
+ if (in->result->storage == STORAGE_INVALID) {
+ return false;
+ }
+ if (!type_is_assignable(ctx, param->type, in->result)) {
+ char *restypename = gen_typename(in->result);
+ char *partypename = gen_typename(param->type);
+ error(ctx, aparam->loc, NULL,
+ "Result value %s is not assignable to parameter type %s",
+ restypename, partypename);
+ free(restypename);
+ free(partypename);
+ return false;
+ }
+ param->default_value = xcalloc(1, sizeof(struct expression));
+ struct expression *cast = lower_implicit_cast(ctx, param->type, in);
+ if (!eval_expr(ctx, cast, param->default_value)) {
+ error(ctx, aparam->loc, NULL,
+ "Unable to evaluate default parameter at compile time");
+ return false;
+ }
+ // TODO remove this check once it works
+ if (param->default_value->result->storage == STORAGE_POINTER &&
+ param->default_value->literal.object != NULL) {
+ error(ctx, aparam->loc, NULL,
+ "Non-null pointer optional parameters are not currently supported. Will fix.");
+ return false;
+ }
+ return true;
+}
+
+static struct dimensions
+type_init_from_atype(struct context *ctx,
+ struct type *type,
+ const struct ast_type *atype)
+{
+ struct type tmp = {0};
+ bool size_only = false;
+ if (type == NULL) {
+ type = &tmp;
+ size_only = true;
+ }
+
+ type->storage = atype->storage;
+
+ struct scope_object *obj = NULL;
+ const struct type *builtin;
+ switch (type->storage) {
+ case STORAGE_INVALID:
+ case STORAGE_FCONST:
+ case STORAGE_ICONST:
+ case STORAGE_RCONST:
+ case STORAGE_ENUM:
+ case STORAGE_NULL:
+ assert(0); // Invariant
+ case STORAGE_DONE:
+ case STORAGE_NEVER:
+ case STORAGE_BOOL:
+ case STORAGE_F32:
+ case STORAGE_F64:
+ case STORAGE_I8:
+ case STORAGE_I16:
+ case STORAGE_I32:
+ case STORAGE_I64:
+ case STORAGE_INT:
+ case STORAGE_NOMEM:
+ case STORAGE_OPAQUE:
+ case STORAGE_RUNE:
+ case STORAGE_SIZE:
+ case STORAGE_STRING:
+ case STORAGE_U8:
+ case STORAGE_U16:
+ case STORAGE_U32:
+ case STORAGE_U64:
+ case STORAGE_UINT:
+ case STORAGE_UINTPTR:
+ case STORAGE_UNDEFINED:
+ case STORAGE_VALIST:
+ case STORAGE_VOID:
+ builtin = builtin_type_for_storage(type->storage);
+ type->size = builtin->size;
+ type->align = builtin->align;
+ break;
+ case STORAGE_ERROR:;
+ struct dimensions dims = { 0 };
+ if (!size_only) {
+ type->error = type_store_lookup_atype(ctx, atype->error);
+ enum type_storage secondary = type_dealias(ctx, type->error)->storage;
+ if (secondary == STORAGE_DONE || secondary == STORAGE_NEVER) {
+ error(ctx, atype->loc, NULL,
+ "%s cannot be an error",
+ type_storage_unparse(secondary));
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+ dims = dim_from_type(type->error);
+ } else {
+ dims = type_store_lookup_dimensions(ctx, atype->error);
+ }
+ type->size = dims.size;
+ type->align = dims.align;
+ break;
+ case STORAGE_ALIAS:
+ obj = scope_lookup(ctx->scope, atype->alias);
+ if (!obj) {
+ char *ident = ident_unparse(atype->alias);
+ error(ctx, atype->loc, NULL,
+ "Unresolvable identifier '%s'", ident);
+ free(ident);
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+
+ if (obj->otype == O_SCAN) {
+ // an incomplete declaration was encountered
+ if (size_only && obj->idecl->type == IDECL_DECL) {
+ wrap_resolver(ctx, obj, resolve_dimensions);
+ type->size = obj->type->size;
+ type->align = obj->type->align;
+ break;
+ }
+ // complete it first and then proceed normally
+ wrap_resolver(ctx, obj, resolve_type);
+ }
+
+ if (obj->otype != O_TYPE) {
+ char *ident = ident_unparse(obj->ident);
+ error(ctx, atype->loc, NULL,
+ "Object '%s' is not a type", ident);
+ free(ident);
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+
+ type->storage = obj->type->storage;
+ if (obj->type->storage == STORAGE_ENUM) {
+ type->_enum = obj->type->_enum;
+ }
+ type->alias.ident = obj->ident;
+ type->alias.name = obj->name;
+ type->alias.type = obj->type->alias.type;
+ type->alias.exported = obj->type->alias.exported;
+ type->size = obj->type->size;
+ type->align = obj->type->align;
+ break;
+ case STORAGE_ARRAY:
+ type->array.length = ast_array_len(ctx, atype);
+ struct dimensions memb = {0};
+ if (size_only) {
+ memb = type_store_lookup_dimensions(ctx,
+ atype->array.members);
+ } else {
+ type->array.members = type_store_lookup_atype(ctx,
+ atype->array.members);
+ memb = dim_from_type(type->array.members);
+ if (type->array.members->storage == STORAGE_INVALID) {
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+ }
+ if (memb.size == 0) {
+ error(ctx, atype->loc, NULL,
+ "Type of size 0 is not a valid array member");
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+ if (memb.size == SIZE_UNDEFINED) {
+ error(ctx, atype->loc, NULL,
+ "Type of undefined size is not a valid array member");
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+
+ type->align = memb.align;
+ if (type->array.length == SIZE_UNDEFINED) {
+ type->size = SIZE_UNDEFINED;
+ } else {
+ type->size = memb.size * type->array.length;
+ if (type->array.length != 0
+ && (type->size < memb.size
+ || type->size < type->array.length)) {
+ error(ctx, atype->loc, NULL, "Type is too big");
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+ }
+ break;
+ case STORAGE_FUNCTION:
+ type->size = SIZE_UNDEFINED;
+ type->align = ALIGN_UNDEFINED;
+ if (size_only) {
+ break;
+ }
+ type->func.result = type_store_lookup_atype(ctx,
+ atype->func.result);
+ type->func.variadism = atype->func.variadism;
+ struct type_func_param *param, **next = &type->func.params;
+ bool has_optional = false;
+ for (struct ast_function_parameters *aparam = atype->func.params;
+ aparam; aparam = aparam->next) {
+ param = *next = xcalloc(1, sizeof(struct type_func_param));
+ param->type = type_store_lookup_atype(ctx, aparam->type);
+ if (param->type->size == SIZE_UNDEFINED) {
+ error(ctx, atype->loc, NULL,
+ "Function parameter types must have defined size");
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+ if (aparam->default_value != NULL) {
+ has_optional = true;
+ if (!default_param_from_atype(ctx,
+ aparam, param)) {
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+ } else if (atype->func.variadism == VARIADISM_HARE
+ && !aparam->next) {
+ param->type = type_store_lookup_slice(
+ ctx, aparam->loc, param->type);
+ } else if (has_optional) {
+ error(ctx, atype->loc, NULL,
+ "Required function parameter may not follow optional parameters");
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+ next = ¶m->next;
+ }
+ break;
+ case STORAGE_POINTER:
+ type->size = builtin_type_uintptr.size;
+ type->align = builtin_type_uintptr.align;
+ if (size_only) {
+ break;
+ }
+ type->pointer.nullable = atype->pointer.nullable;
+ type->pointer.referent = type_store_lookup_atype(
+ ctx, atype->pointer.referent);
+ if (type->pointer.referent->storage == STORAGE_INVALID) {
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+ if (type->pointer.referent->size == 0) {
+ error(ctx, atype->loc, NULL,
+ "Can't have pointer to zero-sized type");
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+ if (type->pointer.referent->storage == STORAGE_NEVER) {
+ error(ctx, atype->loc, NULL,
+ "Can't have pointer to never");
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+ break;
+ case STORAGE_SLICE:
+ type->size = builtin_type_uintptr.size
+ + 2 * builtin_type_size.size;
+ type->align = builtin_type_uintptr.align;
+ if (size_only) {
+ break;
+ }
+ type->array.members = type_store_lookup_atype(ctx,
+ atype->slice.members);
+ if (type->array.members->storage == STORAGE_INVALID) {
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+ if (type->array.members->size == 0) {
+ error(ctx, atype->loc, NULL,
+ "Type of size 0 is not a valid slice member");
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+ if (type->array.members->storage == STORAGE_NEVER) {
+ error(ctx, atype->loc, NULL,
+ "never is not a valid slice member");
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+ type->array.length = SIZE_UNDEFINED;
+ break;
+ case STORAGE_STRUCT:
+ case STORAGE_UNION:
+ if (!struct_init_from_atype(ctx, type, atype, size_only)) {
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+ if (type->storage == STORAGE_UNION || !type->struct_union.packed) {
+ size_t oldsize = type->size;
+ add_padding(&type->size, type->align);
+ if (type->size < oldsize) {
+ error(ctx, atype->loc, NULL, "Type is too big");
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+ }
+ break;
+ case STORAGE_TAGGED:
+ if (size_only) {
+ struct dimensions dims =
+ tagged_size(ctx, &atype->tagged);
+ type->size = dims.size;
+ type->align = dims.align;
+ } else {
+ tagged_init_from_atype(ctx, type, atype);
+ }
+ break;
+ case STORAGE_TUPLE:
+ if (size_only) {
+ struct dimensions tup;
+ tup = tuple_init_from_atype(ctx, NULL, atype);
+ type->size = tup.size;
+ type->align = tup.align;
+ } else {
+ tuple_init_from_atype(ctx, type, atype);
+ }
+ size_t oldsize = type->size;
+ add_padding(&type->size, type->align);
+ if (type->size < oldsize) {
+ error(ctx, atype->loc, NULL, "Type is too big");
+ *type = builtin_type_invalid;
+ return (struct dimensions){0};
+ }
+ break;
+ }
+ return dim_from_type(type);
+}
+
+static const struct type *
+type_store_lookup_type(struct context *ctx, const struct type *type)
+{
+ const struct type *builtin = builtin_for_type(type);
+ if (builtin) {
+ return builtin;
+ }
+
+ uint32_t hash = type_hash(type);
+ struct type_bucket **next = &(*ctx->store)[hash % TYPE_STORE_BUCKETS],
+ *bucket = NULL;
+
+ while (*next) {
+ bucket = *next;
+ if (bucket->type.id == hash) {
+ if (!type_equal(&bucket->type, type)) {
+ next = &bucket->next;
+ continue;
+ }
+ if (bucket->type.storage == STORAGE_ALIAS) {
+ type = type->alias.type;
+ bucket->type.alias.type = type;
+ if (type && type->storage == STORAGE_INVALID) {
+ return &builtin_type_invalid;
+ }
+ }
+ return &bucket->type;
+ }
+ next = &bucket->next;
+ }
+
+ bucket = *next = xcalloc(1, sizeof(struct type_bucket));
+ bucket->type = *type;
+ bucket->type.id = hash;
+ return &bucket->type;
+}
+
+const struct type *
+type_store_lookup_atype(struct context *ctx, const struct ast_type *atype)
+{
+ if (atype->storage == STORAGE_NULL) {
+ return &builtin_type_null;
+ }
+ struct type temp = {0};
+ type_init_from_atype(ctx, &temp, atype);
+ return type_store_lookup_type(ctx, &temp);
+}
+
+// Compute dimensions of an incomplete type without completing it
+struct dimensions
+type_store_lookup_dimensions(struct context *ctx, const struct ast_type *atype)
+{
+ return type_init_from_atype(ctx, NULL, atype);
+}
+
+const struct type *
+type_store_lookup_pointer(struct context *ctx, struct location loc,
+ const struct type *referent, bool nullable)
+{
+ if (referent->storage == STORAGE_INVALID) {
+ return &builtin_type_invalid;
+ }
+ if (referent->storage == STORAGE_NULL) {
+ error(ctx, loc, NULL, "Can't have pointer to bare null");
+ return &builtin_type_invalid;
+ }
+ if (referent->size == 0) {
+ error(ctx, loc, NULL, "Can't have pointer to zero-sized type");
+ return &builtin_type_invalid;
+ }
+ if (referent->storage == STORAGE_NEVER) {
+ error(ctx, loc, NULL, "Can't have pointer to never");
+ return &builtin_type_invalid;
+ }
+ referent = lower_flexible(ctx, referent, NULL);
+
+ struct type ptr = {
+ .storage = STORAGE_POINTER,
+ .pointer = {
+ .referent = referent,
+ .nullable = nullable,
+ },
+ .size = builtin_type_uintptr.size,
+ .align = builtin_type_uintptr.align,
+ };
+ return type_store_lookup_type(ctx, &ptr);
+}
+
+const struct type *
+type_store_lookup_array(struct context *ctx, struct location loc,
+ const struct type *members, size_t len, bool expandable)
+{
+ if (members->storage == STORAGE_INVALID) {
+ return &builtin_type_invalid;
+ }
+ if (members->storage == STORAGE_NULL) {
+ error(ctx, loc, NULL, "Bare null is not a valid array member");
+ return &builtin_type_invalid;
+ }
+ members = lower_flexible(ctx, members, NULL);
+ if (members->size == 0) {
+ error(ctx, loc, NULL,
+ "Type of size 0 is not a valid array member");
+ return &builtin_type_invalid;
+ }
+ if (members->size == SIZE_UNDEFINED) {
+ error(ctx, loc, NULL,
+ "Type of undefined size is not a valid array member");
+ return &builtin_type_invalid;
+ }
+ assert(members->align != 0);
+ assert(members->align != ALIGN_UNDEFINED);
+
+ size_t size = len == SIZE_UNDEFINED
+ ? SIZE_UNDEFINED : members->size * len;
+ if (len != 0 && len != SIZE_UNDEFINED
+ && (size < members->size || size < len)) {
+ error(ctx, loc, NULL, "Type is too big");
+ return &builtin_type_invalid;
+ }
+
+ struct type array = {
+ .storage = STORAGE_ARRAY,
+ .array = {
+ .members = members,
+ .length = len,
+ // TODO: Define expandable semantics better in spec
+ .expandable = expandable,
+ },
+ .size = size,
+ .align = members->align,
+ };
+ return type_store_lookup_type(ctx, &array);
+}
+
+const struct type *
+type_store_lookup_slice(struct context *ctx, struct location loc,
+ const struct type *members)
+{
+ if (members->storage == STORAGE_INVALID) {
+ return &builtin_type_invalid;
+ }
+ assert(members->storage != STORAGE_NULL);
+ members = lower_flexible(ctx, members, NULL);
+ if (members->size == 0) {
+ error(ctx, loc, NULL,
+ "Type of size 0 is not a valid slice member");
+ return &builtin_type_invalid;
+ }
+ assert(members->align != 0);
+
+ struct type slice = {
+ .storage = STORAGE_SLICE,
+ .array = {
+ .members = members,
+ .length = SIZE_UNDEFINED,
+ },
+ .size = builtin_type_uintptr.size + 2 * builtin_type_size.size,
+ .align = builtin_type_uintptr.align,
+ };
+ return type_store_lookup_type(ctx, &slice);
+}
+
+const struct type *
+type_store_lookup_alias(struct context *ctx, struct ident *ident,
+ struct ident *name, const struct type *secondary, bool exported)
+{
+ struct type type = {
+ .storage = STORAGE_ALIAS,
+ .alias.type = secondary,
+ .alias.ident = ident,
+ .alias.name = name,
+ .alias.exported = exported,
+ };
+ return type_store_lookup_type(ctx, &type);
+}
+
+static struct type
+lookup_tagged(struct context *ctx, struct location loc,
+ struct type_tagged_union *tagged, bool valid)
+{
+ struct type ret = {
+ .storage = STORAGE_TAGGED,
+ .tagged = tagged_dup_tags(tagged),
+ };
+ tagged_init(ctx, &ret, loc, valid);
+ return ret;
+}
+
+const struct type *
+type_store_lookup_tagged(struct context *ctx, struct location loc,
+ struct type_tagged_union *tagged)
+{
+ struct type temp = lookup_tagged(ctx, loc, tagged, true);
+ switch (temp.tagged.len) {
+ case 0:
+ return &builtin_type_never;
+ case 1:
+ return temp.tagged.types[0];
+ default:
+ return type_store_lookup_type(ctx, &temp);
+ }
+}
+
+const struct type *
+type_store_lookup_tuple(struct context *ctx, struct location loc,
+ struct type_tuple *values)
+{
+ struct type type = {
+ .storage = STORAGE_TUPLE,
+ };
+ for (struct type_tuple *t = values; t; t = t->next) {
+ if (t->type->storage == STORAGE_INVALID) {
+ return &builtin_type_invalid;
+ }
+ if (t->type->storage == STORAGE_NULL) {
+ error(ctx, loc, NULL, "Bare null is not a valid tuple member");
+ return &builtin_type_invalid;
+ }
+ t->type = lower_flexible(ctx, t->type, NULL);
+ if (t->type->size == SIZE_UNDEFINED) {
+ error(ctx, loc, NULL,
+ "Type of undefined size is not a valid tuple member");
+ return &builtin_type_invalid;
+ }
+ assert(t->type->align != ALIGN_UNDEFINED);
+
+ if (t->type->align > type.align) {
+ type.align = t->type->align;
+ }
+ t->offset = type.size;
+ if (t->type->align != 0) {
+ add_padding(&t->offset, t->type->align);
+ }
+ if (t->offset < type.size || t->offset + t->type->size < t->offset) {
+ error(ctx, loc, NULL, "Type is too big");
+ return &builtin_type_invalid;
+ }
+ type.size = t->offset + t->type->size;
+ }
+ type.tuple = *values;
+
+ size_t oldsize = type.size;
+ add_padding(&type.size, type.align);
+ if (type.size < oldsize) {
+ error(ctx, loc, NULL, "Type is too big");
+ return &builtin_type_invalid;
+ }
+ return type_store_lookup_type(ctx, &type);
+}
+
+const struct type *
+type_store_lookup_enum(struct context *ctx, const struct ast_type *atype,
+ bool exported)
+{
+ struct type type = {0};
+ type.storage = STORAGE_ENUM;
+ type.alias.ident = mkident(ctx, atype->alias, NULL);
+ type.alias.name = atype->alias;
+ type.alias.exported = exported;
+ type.alias.type = builtin_type_for_storage(atype->_enum.storage);
+ if (!type_is_integer(ctx, type.alias.type)
+ && type.alias.type->storage != STORAGE_RUNE) {
+ error(ctx, atype->loc, NULL,
+ "Enum storage must be an integer or rune");
+ return &builtin_type_invalid;
+ }
+ type.size = type.alias.type->size;
+ type.align = type.alias.type->size;
+ return type_store_lookup_type(ctx, &type);
+}
+
+static void
+expand_tagged(struct type_tagged_union *out, const struct type_tagged_union *in)
+{
+ for (size_t i = 0; i < in->len; i++) {
+ if (in->types[i]->storage == STORAGE_TAGGED) {
+ expand_tagged(out, &in->types[i]->tagged);
+ } else {
+ tagged_append(out, in->types[i]);
+ }
+ }
+}
+
+// Algorithm:
+// - Deduplicate and collect nested unions
+// - Remove never
+// - Merge *type with nullable *type
+// - If one of the types is null:
+// - If there's more than one pointer type, error out
+// - If there's one pointer type, make it nullable and drop the null
+// - If there are no pointer types, keep the null
+// - If the resulting union only has one type, return that type
+// - Otherwise, if no types remain, return never
+// - Otherwise, return a tagged union of all the selected types
+const struct type *
+type_store_reduce_result(struct context *ctx, struct location loc,
+ struct type_tagged_union *in)
+{
+ if (!in || in->len == 0) {
+ return &builtin_type_never;
+ } else if (in->len == 1) {
+ return in->types[0];
+ }
+
+ struct type_tagged_union expanded = { .types = NULL };
+ expand_tagged(&expanded, in);
+ struct type type = lookup_tagged(ctx, loc, &expanded, false);
+
+ size_t ptr_index = 0;
+ size_t nptrs = 0;
+ bool have_null = false;
+ size_t new_len = 0;
+ for (size_t i = 0; i < type.tagged.len; i++) {
+ const struct type *memb = type.tagged.types[i];
+ if (memb->storage == STORAGE_NEVER || memb->storage == STORAGE_INVALID) {
+ continue;
+ }
+ if (memb->storage == STORAGE_NULL) {
+ have_null = true;
+ continue;
+ }
+ if (memb->storage != STORAGE_POINTER) {
+ type.tagged.types[new_len++] = memb;
+ continue;
+ }
+ bool drop = false;
+ for (size_t j = 0; j < i; j++) {
+ const struct type *other = type.tagged.types[j];
+ if (other->storage != STORAGE_POINTER) {
+ continue;
+ }
+ // XXX: Why are we comparing IDs here?
+ if (memb->pointer.referent->id != other->pointer.referent->id) {
+ continue;
+ }
+ if (!memb->pointer.nullable && !other->pointer.nullable) {
+ continue;
+ }
+ const struct type *_memb = type_store_lookup_pointer(ctx,
+ loc, memb->pointer.referent, true);
+ other = type_store_lookup_pointer(ctx, loc,
+ other->pointer.referent, true);
+ if (_memb == other) {
+ type.tagged.types[j] = other;
+ drop = true;
+ break;
+ }
+ }
+ if (!drop) {
+ ptr_index = new_len;
+ type.tagged.types[new_len++] = memb;
+ nptrs++;
+ }
+ }
+ type.tagged.len = new_len;
+
+ if (have_null) {
+ if (nptrs != 1) {
+ error(ctx, loc, NULL,
+ "Invalid result type (dangling or ambiguous null)");
+ return &builtin_type_invalid;
+ }
+ // XXX: Flags?
+ type.tagged.types[ptr_index] = type_store_lookup_pointer(ctx, loc,
+ type.tagged.types[ptr_index]->pointer.referent, true);
+ }
+
+ return type_store_lookup_tagged(ctx, loc, &type.tagged);
+}
diff --git a/ref/harec/src/types.c b/ref/harec/src/types.c
new file mode 100644
index 00000000..0f888eab
--- /dev/null
+++ b/ref/harec/src/types.c
@@ -0,0 +1,1507 @@
+#include
+#include
+#include
+#include
+#include
+#include "arch.h"
+#include "check.h"
+#include "expr.h"
+#include "scope.h"
+#include "types.h"
+#include "util.h"
+
+const struct type *
+type_dereference(struct context *ctx, const struct type *type, bool allow_nullable)
+{
+ switch (type->storage) {
+ case STORAGE_ALIAS:
+ case STORAGE_ERROR:
+ if (type_dealias(ctx, type)->storage != STORAGE_POINTER) {
+ return type;
+ }
+ return type_dereference(ctx, type_dealias(ctx, type), allow_nullable);
+ case STORAGE_POINTER:
+ if (!allow_nullable && type->pointer.nullable) {
+ return NULL;
+ }
+ return type_dereference(ctx, type->pointer.referent, allow_nullable);
+ default:
+ return type;
+ }
+}
+
+static const struct scope_object *
+complete_alias(struct context *ctx, struct type *type)
+{
+ assert(type->storage == STORAGE_ALIAS);
+ const struct scope_object *obj =
+ scope_lookup(ctx->scope, type->alias.name);
+ assert(obj != NULL);
+ assert(obj->otype == O_TYPE || obj->otype == O_SCAN);
+ assert(obj->idecl->type == IDECL_DECL);
+
+ if (!obj->idecl->dealias_in_progress) {
+ obj->idecl->dealias_in_progress = true;
+ type->alias.type = type_store_lookup_atype(
+ ctx, obj->idecl->decl.type.type);
+ obj->idecl->dealias_in_progress = false;
+ }
+ return obj;
+}
+
+const struct type *
+type_dealias(struct context *ctx, const struct type *_type)
+{
+ struct type *type = (struct type *)_type;
+ while (type->storage == STORAGE_ALIAS || type->storage == STORAGE_ERROR) {
+ if (type->storage == STORAGE_ERROR) {
+ type = (struct type *)type->error;
+ continue;
+ }
+ if (type->alias.type == NULL) {
+ // gen et al. don't have access to the check context,
+ // but by that point all aliases should already be fully
+ // scanned
+ assert(ctx != NULL);
+ const struct scope_object *obj =
+ complete_alias(ctx, type);
+ if (type->alias.type == NULL) {
+ char *identstr = ident_unparse(obj->name);
+ error(ctx, obj->idecl->decl.loc, NULL,
+ "Circular dependency for '%s'",
+ identstr);
+ free(identstr);
+ type->alias.type = &builtin_type_invalid;
+ }
+ }
+ type = (struct type *)type->alias.type;
+ }
+ return type;
+}
+
+const struct type *
+strip_error(const struct type *type)
+{
+ while (type->storage == STORAGE_ERROR) {
+ type = type->error;
+ }
+ return type;
+}
+
+// checks if a type is `done`, or an alias thereof, without erroring out when a
+// "circular dependency" is encountered (since that means the type isn't `done`)
+bool
+type_is_done(struct context *ctx, const struct type *type)
+{
+ while (type->storage == STORAGE_ALIAS) {
+ if (type->alias.type == NULL) {
+ complete_alias(ctx, (struct type *)type);
+ if (type->alias.type == NULL) {
+ return false;
+ }
+ }
+ type = type->alias.type;
+ }
+ return type->storage == STORAGE_DONE;
+}
+
+const struct struct_field *
+type_get_field(struct context *ctx, const struct type *type, const char *name)
+{
+ if (type->storage == STORAGE_INVALID) {
+ return NULL;
+ }
+ assert(type->storage == STORAGE_STRUCT
+ || type->storage == STORAGE_UNION);
+ assert(strcmp(name, "_") != 0);
+ struct struct_field *field = type->struct_union.fields;
+ while (field) {
+ if (field->name) {
+ if (strcmp(field->name, name) == 0) {
+ return field;
+ }
+ } else {
+ const struct struct_field *f = type_get_field(ctx,
+ type_dealias(ctx, field->type), name);
+ if (f != NULL) {
+ return f;
+ }
+ }
+ field = field->next;
+ }
+ return NULL;
+}
+
+const struct type_tuple *
+type_get_value(const struct type *type, uint64_t index)
+{
+ assert(type->storage == STORAGE_TUPLE);
+ const struct type_tuple *tuple = &type->tuple;
+ while (tuple) {
+ if (index == 0) {
+ return tuple;
+ }
+ tuple = tuple->next;
+ --index;
+ }
+ return NULL;
+}
+
+bool
+type_is_error(struct context *ctx, const struct type *type)
+{
+ while (type->storage == STORAGE_ALIAS) {
+ // Complete the alias
+ type_dealias(ctx, type);
+ type = type->alias.type;
+ }
+ return type->storage == STORAGE_ERROR || type->storage == STORAGE_NOMEM;
+}
+
+// Returns true if this type is or contains an error type
+bool
+type_has_error(struct context *ctx, const struct type *type)
+{
+ if (type_is_error(ctx, type)) {
+ return true;
+ }
+ type = type_dealias(ctx, type);
+ if (type->storage != STORAGE_TAGGED) {
+ return false;
+ }
+ for (size_t i = 0; i < type->tagged.len; i++) {
+ if (type_is_error(ctx, type->tagged.types[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+const char *
+type_storage_unparse(enum type_storage storage)
+{
+ switch (storage) {
+ case STORAGE_ALIAS:
+ return "alias";
+ case STORAGE_ARRAY:
+ return "array";
+ case STORAGE_BOOL:
+ return "bool";
+ case STORAGE_ENUM:
+ return "enum";
+ case STORAGE_ERROR:
+ return "error";
+ case STORAGE_F32:
+ return "f32";
+ case STORAGE_F64:
+ return "f64";
+ case STORAGE_INVALID:
+ return "invalid";
+ case STORAGE_FCONST:
+ return "flexible float";
+ case STORAGE_FUNCTION:
+ return "function";
+ case STORAGE_I16:
+ return "i16";
+ case STORAGE_I32:
+ return "i32";
+ case STORAGE_I64:
+ return "i64";
+ case STORAGE_I8:
+ return "i8";
+ case STORAGE_ICONST:
+ return "flexible integer";
+ case STORAGE_INT:
+ return "int";
+ case STORAGE_NEVER:
+ return "never";
+ case STORAGE_NOMEM:
+ return "nomem";
+ case STORAGE_NULL:
+ return "null";
+ case STORAGE_OPAQUE:
+ return "opaque";
+ case STORAGE_POINTER:
+ return "pointer";
+ case STORAGE_RCONST:
+ return "flexible rune";
+ case STORAGE_RUNE:
+ return "rune";
+ case STORAGE_SIZE:
+ return "size";
+ case STORAGE_SLICE:
+ return "slice";
+ case STORAGE_STRING:
+ return "str";
+ case STORAGE_STRUCT:
+ return "struct";
+ case STORAGE_TAGGED:
+ return "tagged union";
+ case STORAGE_TUPLE:
+ return "tuple";
+ case STORAGE_U16:
+ return "u16";
+ case STORAGE_U32:
+ return "u32";
+ case STORAGE_U64:
+ return "u64";
+ case STORAGE_U8:
+ return "u8";
+ case STORAGE_UINT:
+ return "uint";
+ case STORAGE_UINTPTR:
+ return "uintptr";
+ case STORAGE_UNION:
+ return "union";
+ case STORAGE_UNDEFINED:
+ return "undefined";
+ case STORAGE_VALIST:
+ return "valist";
+ case STORAGE_VOID:
+ return "void";
+ case STORAGE_DONE:
+ return "done";
+ }
+ assert(0);
+}
+
+bool
+type_is_integer(struct context *ctx, const struct type *type)
+{
+ switch (type->storage) {
+ case STORAGE_VOID:
+ case STORAGE_DONE:
+ case STORAGE_ARRAY:
+ case STORAGE_FUNCTION:
+ case STORAGE_NEVER:
+ case STORAGE_NOMEM:
+ case STORAGE_OPAQUE:
+ case STORAGE_POINTER:
+ case STORAGE_SLICE:
+ case STORAGE_STRING:
+ case STORAGE_STRUCT:
+ case STORAGE_TAGGED:
+ case STORAGE_TUPLE:
+ case STORAGE_UNION:
+ case STORAGE_BOOL:
+ case STORAGE_NULL:
+ case STORAGE_RCONST:
+ case STORAGE_RUNE:
+ case STORAGE_F32:
+ case STORAGE_F64:
+ case STORAGE_FCONST:
+ case STORAGE_VALIST:
+ case STORAGE_UNDEFINED:
+ return false;
+ case STORAGE_ENUM:
+ case STORAGE_INVALID:
+ case STORAGE_I8:
+ case STORAGE_I16:
+ case STORAGE_I32:
+ case STORAGE_I64:
+ case STORAGE_ICONST:
+ case STORAGE_INT:
+ case STORAGE_SIZE:
+ case STORAGE_U8:
+ case STORAGE_U16:
+ case STORAGE_U32:
+ case STORAGE_U64:
+ case STORAGE_UINT:
+ case STORAGE_UINTPTR:
+ return true;
+ case STORAGE_ALIAS:
+ case STORAGE_ERROR:
+ return type_is_integer(ctx, type_dealias(ctx, type));
+ }
+ assert(0); // Unreachable
+}
+
+bool
+type_is_numeric(struct context *ctx, const struct type *type)
+{
+ switch (type->storage) {
+ case STORAGE_VOID:
+ case STORAGE_DONE:
+ case STORAGE_ARRAY:
+ case STORAGE_FUNCTION:
+ case STORAGE_NEVER:
+ case STORAGE_NOMEM:
+ case STORAGE_OPAQUE:
+ case STORAGE_POINTER:
+ case STORAGE_SLICE:
+ case STORAGE_STRING:
+ case STORAGE_STRUCT:
+ case STORAGE_TAGGED:
+ case STORAGE_TUPLE:
+ case STORAGE_UNION:
+ case STORAGE_BOOL:
+ case STORAGE_RCONST:
+ case STORAGE_RUNE:
+ case STORAGE_NULL:
+ case STORAGE_VALIST:
+ case STORAGE_UNDEFINED:
+ return false;
+ case STORAGE_INVALID:
+ case STORAGE_ENUM:
+ case STORAGE_I8:
+ case STORAGE_I16:
+ case STORAGE_I32:
+ case STORAGE_I64:
+ case STORAGE_ICONST:
+ case STORAGE_INT:
+ case STORAGE_F32:
+ case STORAGE_F64:
+ case STORAGE_FCONST:
+ case STORAGE_SIZE:
+ case STORAGE_U8:
+ case STORAGE_U16:
+ case STORAGE_U32:
+ case STORAGE_U64:
+ case STORAGE_UINT:
+ case STORAGE_UINTPTR:
+ return true;
+ case STORAGE_ALIAS:
+ case STORAGE_ERROR:
+ return type_is_numeric(ctx, type_dealias(ctx, type));
+ }
+ assert(0); // Unreachable
+}
+
+bool
+type_is_float(struct context *ctx, const struct type *type)
+{
+ type = type_dealias(ctx, type);
+ return type->storage == STORAGE_F32 || type->storage == STORAGE_F64
+ || type->storage == STORAGE_FCONST
+ || type->storage == STORAGE_INVALID;
+}
+
+bool
+type_is_signed(struct context *ctx, const struct type *type)
+{
+ enum type_storage storage = type_dealias(ctx, type)->storage;
+ if (storage == STORAGE_ENUM) {
+ storage = type_dealias(ctx, type)->alias.type->storage;
+ }
+ switch (storage) {
+ case STORAGE_VOID:
+ case STORAGE_DONE:
+ case STORAGE_ARRAY:
+ case STORAGE_ENUM:
+ case STORAGE_INVALID: // XXX?
+ case STORAGE_FUNCTION:
+ case STORAGE_NEVER:
+ case STORAGE_NOMEM:
+ case STORAGE_OPAQUE:
+ case STORAGE_POINTER:
+ case STORAGE_SLICE:
+ case STORAGE_STRING:
+ case STORAGE_STRUCT:
+ case STORAGE_TAGGED:
+ case STORAGE_TUPLE:
+ case STORAGE_BOOL:
+ case STORAGE_RCONST:
+ case STORAGE_RUNE:
+ case STORAGE_NULL:
+ case STORAGE_SIZE:
+ case STORAGE_U8:
+ case STORAGE_U16:
+ case STORAGE_U32:
+ case STORAGE_U64:
+ case STORAGE_UINT:
+ case STORAGE_UINTPTR:
+ case STORAGE_UNION:
+ case STORAGE_UNDEFINED:
+ case STORAGE_VALIST:
+ return false;
+ case STORAGE_I8:
+ case STORAGE_I16:
+ case STORAGE_I32:
+ case STORAGE_I64:
+ case STORAGE_INT:
+ case STORAGE_F32:
+ case STORAGE_F64:
+ case STORAGE_FCONST:
+ return true;
+ case STORAGE_ICONST:
+ return type->flexible.min < 0;
+ case STORAGE_ALIAS:
+ case STORAGE_ERROR:
+ assert(0); // Handled above
+ }
+ assert(0); // Unreachable
+}
+
+bool
+type_is_flexible(const struct type *type)
+{
+ return type->storage == STORAGE_FCONST
+ || type->storage == STORAGE_ICONST
+ || type->storage == STORAGE_RCONST;
+}
+
+uint32_t
+type_hash(const struct type *type)
+{
+ uint32_t hash = FNV1A_INIT;
+ hash = fnv1a(hash, type->storage);
+ switch (type->storage) {
+ case STORAGE_VOID:
+ return 0;
+ case STORAGE_BOOL:
+ case STORAGE_INVALID:
+ case STORAGE_F32:
+ case STORAGE_F64:
+ case STORAGE_I8:
+ case STORAGE_I16:
+ case STORAGE_I32:
+ case STORAGE_I64:
+ case STORAGE_INT:
+ case STORAGE_NEVER:
+ case STORAGE_NOMEM:
+ case STORAGE_NULL:
+ case STORAGE_OPAQUE:
+ case STORAGE_RUNE:
+ case STORAGE_SIZE:
+ case STORAGE_U8:
+ case STORAGE_U16:
+ case STORAGE_U32:
+ case STORAGE_U64:
+ case STORAGE_UINT:
+ case STORAGE_UINTPTR:
+ case STORAGE_UNDEFINED:
+ case STORAGE_VALIST:
+ case STORAGE_DONE:
+ case STORAGE_STRING:
+ break; // built-ins
+ case STORAGE_ENUM:
+ hash = fnv1a(hash, type->alias.type->storage);
+ /* fallthrough */
+ case STORAGE_ALIAS:
+ hash = ident_hash(hash, type->alias.ident);
+ break;
+ case STORAGE_ERROR:
+ hash = fnv1a_u32(hash, type_hash(type->error));
+ break;
+ case STORAGE_ARRAY:
+ hash = fnv1a_u32(hash, type_hash(type->array.members));
+ hash = fnv1a_size(hash, type->array.length);
+ hash = fnv1a_u32(hash, type->array.expandable);
+ break;
+ case STORAGE_FUNCTION:
+ hash = fnv1a_u32(hash, type_hash(type->func.result));
+ hash = fnv1a(hash, type->func.variadism);
+ for (struct type_func_param *param = type->func.params;
+ param; param = param->next) {
+ hash = fnv1a_u32(hash, type_hash(param->type));
+ if (param->default_value) {
+ hash = fnv1a_u32(hash, expr_hash(
+ param->default_value));
+ }
+ }
+ break;
+ case STORAGE_FCONST:
+ case STORAGE_ICONST:
+ case STORAGE_RCONST:
+ hash = fnv1a(hash, type->flexible.id);
+ break;
+ case STORAGE_POINTER:
+ hash = fnv1a(hash, (unsigned char) type->pointer.nullable);
+ hash = fnv1a_u32(hash, type_hash(type->pointer.referent));
+ break;
+ case STORAGE_SLICE:
+ hash = fnv1a_u32(hash, type_hash(type->array.members));
+ break;
+ case STORAGE_STRUCT:
+ case STORAGE_UNION:
+ hash = fnv1a_size(hash, type->struct_union.packed);
+ for (const struct struct_field *field = type->struct_union.fields;
+ field; field = field->next) {
+ if (field->name) {
+ hash = fnv1a_s(hash, field->name);
+ }
+ hash = fnv1a_u32(hash, type_hash(field->type));
+ hash = fnv1a_size(hash, field->offset);
+ }
+ break;
+ case STORAGE_TAGGED:
+ // Invariant: subtypes must be sorted by ID and must not include
+ // any other tagged union types, nor any duplicates.
+ for (size_t i = 0; i < type->tagged.len; i++) {
+ hash = fnv1a_u32(hash, type_hash(type->tagged.types[i]));
+ }
+ break;
+ case STORAGE_TUPLE:
+ for (const struct type_tuple *tuple = &type->tuple;
+ tuple; tuple = tuple->next) {
+ hash = fnv1a_u32(hash, type_hash(tuple->type));
+ }
+ break;
+ }
+ return hash;
+}
+
+bool
+type_equal(const struct type *a, const struct type *b)
+{
+ if (a->storage != b->storage) {
+ return false;
+ }
+
+ switch (a->storage) {
+ case STORAGE_BOOL:
+ case STORAGE_DONE:
+ case STORAGE_F32:
+ case STORAGE_F64:
+ case STORAGE_I16:
+ case STORAGE_I32:
+ case STORAGE_I64:
+ case STORAGE_I8:
+ case STORAGE_INT:
+ case STORAGE_NEVER:
+ case STORAGE_NOMEM:
+ case STORAGE_NULL:
+ case STORAGE_OPAQUE:
+ case STORAGE_RUNE:
+ case STORAGE_SIZE:
+ case STORAGE_STRING:
+ case STORAGE_U16:
+ case STORAGE_U32:
+ case STORAGE_U64:
+ case STORAGE_U8:
+ case STORAGE_UINT:
+ case STORAGE_UINTPTR:
+ case STORAGE_UNDEFINED:
+ case STORAGE_VOID:
+ case STORAGE_INVALID:
+ case STORAGE_VALIST:
+ return true;
+ case STORAGE_ALIAS:
+ case STORAGE_ENUM:
+ return ident_equal(a->alias.ident, b->alias.ident);
+ case STORAGE_ERROR:
+ return type_equal(a->error, b->error);
+ case STORAGE_ARRAY:
+ case STORAGE_SLICE:
+ return a->array.length == b->array.length
+ && a->array.expandable == b->array.expandable
+ && type_equal(a->array.members, b->array.members);
+ case STORAGE_FUNCTION:
+ if (!type_equal(a->func.result, b->func.result)) {
+ return false;
+ }
+ if (a->func.variadism != b->func.variadism) {
+ return false;
+ }
+ const struct type_func_param *param_a = a->func.params;
+ const struct type_func_param *param_b = b->func.params;
+ while (param_a && param_b) {
+ if (!type_equal(param_a->type, param_b->type)) {
+ return false;
+ }
+ if (param_a->default_value || param_b->default_value) {
+ if (!param_a->default_value
+ || !param_b->default_value) {
+ return false;
+ }
+ if (!expr_equal(param_a->default_value,
+ param_b->default_value)) {
+ return false;
+ }
+ }
+ param_a = param_a->next;
+ param_b = param_b->next;
+ }
+ return !param_a && !param_b;
+ case STORAGE_POINTER:
+ return a->pointer.nullable == b->pointer.nullable
+ && type_equal(a->pointer.referent, b->pointer.referent);
+ case STORAGE_STRUCT:
+ case STORAGE_UNION:
+ if (a->struct_union.packed != b->struct_union.packed) {
+ return false;
+ }
+ const struct struct_field *field_a = a->struct_union.fields;
+ const struct struct_field *field_b = b->struct_union.fields;
+ while (field_a && field_b) {
+ if (field_a->name || field_b->name) {
+ if (!field_a->name || !field_b->name) {
+ return false;
+ }
+ if (strcmp(field_a->name, field_b->name)) {
+ return false;
+ }
+ }
+ if (!type_equal(field_a->type, field_b->type)) {
+ return false;
+ }
+ if (field_a->offset != field_b->offset) {
+ return false;
+ }
+ field_a = field_a->next;
+ field_b = field_b->next;
+ }
+ return !field_a && !field_b;
+ case STORAGE_TAGGED:
+ if (a->tagged.len != b->tagged.len) {
+ return false;
+ }
+ for (size_t i = 0; i < a->tagged.len; i++) {
+ if (!type_equal(a->tagged.types[i], b->tagged.types[i])) {
+ return false;
+ }
+ }
+ return true;
+ case STORAGE_TUPLE:;
+ const struct type_tuple *tuple_a = &a->tuple;
+ const struct type_tuple *tuple_b = &b->tuple;
+ while (tuple_a && tuple_b) {
+ if (!type_equal(tuple_a->type, tuple_b->type)) {
+ return false;
+ }
+ tuple_a = tuple_a->next;
+ tuple_b = tuple_b->next;
+ }
+ return !tuple_a && !tuple_b;
+ case STORAGE_FCONST:
+ case STORAGE_ICONST:
+ case STORAGE_RCONST:
+ return a == b;
+ }
+ assert(0); // Unreachable
+}
+
+void
+tagged_append(struct type_tagged_union *tagged, const struct type *memb)
+{
+ if (tagged->len == tagged->cap) {
+ tagged->cap++;
+ tagged->cap *= 2;
+ tagged->types = xrealloc(tagged->types,
+ tagged->cap * sizeof(struct type *));
+ }
+ assert(tagged->len < tagged->cap);
+ tagged->types[tagged->len] = memb;
+ tagged->len++;
+}
+
+// Duplicate and return the tags of a tagged union
+struct type_tagged_union
+tagged_dup_tags(const struct type_tagged_union *tags)
+{
+ const struct type **types = xcalloc(tags->len, sizeof(struct type *));
+ memcpy(types, tags->types, tags->len * sizeof(struct type *));
+ return (struct type_tagged_union){
+ .types = types,
+ .len = tags->len,
+ .cap = tags->len,
+ };
+}
+
+const struct type *
+tagged_select_subtype(struct context *ctx, const struct type *tagged,
+ const struct type *subtype, bool strip)
+{
+ tagged = type_dealias(ctx, tagged);
+ assert(tagged->storage == STORAGE_TAGGED);
+
+ const struct type *stripped = strip_error(subtype);
+
+ size_t nassign = 0;
+ const struct type *selected = NULL;
+ for (size_t i = 0; i < tagged->tagged.len; i++) {
+ const struct type *t = tagged->tagged.types[i];
+ if (t->id == subtype->id) {
+ return t;
+ }
+
+ if (type_is_assignable(ctx, t, subtype)) {
+ selected = t;
+ ++nassign;
+ }
+ }
+
+ if (strip) {
+ for (size_t i = 0; i < tagged->tagged.len; i++) {
+ const struct type *tustripped =
+ strip_error(tagged->tagged.types[i]);
+ if (tustripped->id == stripped->id) {
+ return tagged->tagged.types[i];
+ }
+ }
+ }
+
+ if (nassign == 1) {
+ return selected;
+ }
+
+ return NULL;
+}
+
+static int64_t
+min_value(struct context *ctx, const struct type *t)
+{
+ assert(type_is_integer(ctx, t));
+ if (!type_is_signed(ctx, t)) {
+ return 0;
+ }
+ if (t->size == sizeof(int64_t)) {
+ return INT64_MIN;
+ }
+ return -((int64_t)1 << (t->size * 8 - 1));
+}
+
+static uint64_t
+max_value(struct context *ctx, const struct type *t)
+{
+ assert(type_is_integer(ctx, t));
+ size_t bits = t->size * 8;
+ if (type_is_signed(ctx, t)) {
+ bits--;
+ }
+ if (bits == sizeof(uint64_t) * 8) {
+ return UINT64_MAX;
+ }
+ return ((uint64_t)1 << bits) - 1;
+}
+
+const struct type *
+type_create_flexible(enum type_storage storage, int64_t min, int64_t max)
+{
+ // XXX: This'll be impossible to free. The right solution would be to
+ // store iconsts in the type store, but that'd require passing the store
+ // into type_is_assignable et al. An easier solution would be to keep
+ // our own list of iconsts and free them separately. Whatever, it
+ // doesn't really matter that much.
+ static uint32_t id = 0;
+ struct type *type = xcalloc(1, sizeof(struct type));
+ type->storage = storage;
+ type->size = SIZE_UNDEFINED;
+ type->align = ALIGN_UNDEFINED;
+ type->flexible.min = min;
+ type->flexible.max = max;
+ type->flexible.id = id++;
+ type->id = type_hash(type);
+ assert(type_is_flexible(type));
+ return type;
+}
+
+// Register a reference to a flexible type. When `type` is lowered in
+// [[lower_flexible]], *ref will be updated to point to the new type.
+void
+flexible_refer(const struct type *type, const struct type **ref)
+{
+ if (type == NULL || !type_is_flexible(type)) {
+ return;
+ }
+ struct type_flexible *flex = (struct type_flexible *)&type->flexible;
+
+ if (flex->nrefs >= flex->zrefs) {
+ flex->zrefs *= 2;
+ if (flex->zrefs == 0) {
+ flex->zrefs++;
+ }
+ flex->refs = xrealloc(flex->refs,
+ flex->zrefs * sizeof(const struct type **));
+ }
+ flex->refs[flex->nrefs] = ref;
+ flex->nrefs++;
+}
+
+// Sets the number of references for a flexible type to zero.
+void
+flexible_reset_refs(const struct type *type)
+{
+ if (type == NULL || !type_is_flexible(type)) {
+ return;
+ }
+ ((struct type *)type)->flexible.nrefs = 0;
+}
+
+// Lower a flexible type. If new == NULL, lower it to its default type.
+const struct type *
+lower_flexible(struct context *ctx, const struct type *old, const struct type *new) {
+ if (!type_is_flexible(old)) {
+ // If new != NULL, we're expected to always do something, and we
+ // can't if it's not flexible
+ assert(new == NULL);
+ return old;
+ }
+ if (new == NULL) {
+ switch (old->storage) {
+ case STORAGE_FCONST:
+ new = &builtin_type_f64;
+ break;
+ case STORAGE_ICONST:
+ if (old->flexible.max <= (int64_t)max_value(ctx, &builtin_type_int)
+ && old->flexible.min >= min_value(ctx, &builtin_type_int)) {
+ new = &builtin_type_int;
+ } else {
+ new = &builtin_type_i64;
+ }
+ break;
+ case STORAGE_RCONST:
+ new = &builtin_type_rune;
+ break;
+ default:
+ assert(0);
+ }
+ }
+ for (size_t i = 0; i < old->flexible.nrefs; i++) {
+ flexible_refer(new, old->flexible.refs[i]);
+ *old->flexible.refs[i] = new;
+ }
+ // XXX: Can we free old?
+ return new;
+}
+
+// Implements the flexible type promotion algorithm
+const struct type *
+promote_flexible(struct context *ctx,
+ const struct type *a, const struct type *b) {
+ if (a->storage == STORAGE_ICONST && b->storage == STORAGE_ICONST) {
+ int64_t min = a->flexible.min < b->flexible.min
+ ? a->flexible.min : b->flexible.min;
+ int64_t max = a->flexible.max > b->flexible.max
+ ? a->flexible.max : b->flexible.max;
+ const struct type *l =
+ type_create_flexible(STORAGE_ICONST, min, max);
+ lower_flexible(ctx, a, l);
+ lower_flexible(ctx, b, l);
+ return l;
+ }
+ if (type_is_flexible(a)) {
+ if (a->storage == b->storage) {
+ const struct type *l =
+ type_create_flexible(a->storage, 0, 0);
+ lower_flexible(ctx, a, l);
+ lower_flexible(ctx, b, l);
+ return l;
+ }
+ if (type_is_flexible(b)) {
+ return NULL;
+ }
+ return promote_flexible(ctx, b, a);
+ }
+ assert(!type_is_flexible(a) && type_is_flexible(b));
+ if (type_dealias(ctx, a)->storage == STORAGE_TAGGED) {
+ const struct type *tag = NULL;
+ struct type_tagged_union tagged = type_dealias(ctx, a)->tagged;
+ for (size_t i = 0; i < tagged.len; i++) {
+ const struct type *p =
+ promote_flexible(ctx, tagged.types[i], b);
+ if (!p) {
+ lower_flexible(ctx, b, tag);
+ continue;
+ }
+ if (tag) {
+ // Ambiguous
+ b = lower_flexible(ctx, b, NULL);
+ if (type_is_assignable(ctx, a, b)) {
+ return b;
+ }
+ return NULL;
+ }
+ tag = p;
+ }
+ return tag;
+ }
+ switch (b->storage) {
+ case STORAGE_FCONST:
+ if (!type_is_float(ctx, a)) {
+ return NULL;
+ }
+ lower_flexible(ctx, b, a);
+ return a;
+ case STORAGE_ICONST:
+ if (!type_is_integer(ctx, a)) {
+ return NULL;
+ }
+ if (type_is_signed(ctx, a) && min_value(ctx, a) > b->flexible.min) {
+ return NULL;
+ }
+ if (b->flexible.max > 0 && max_value(ctx, a) < (uint64_t)b->flexible.max) {
+ return NULL;
+ }
+ lower_flexible(ctx, b, a);
+ return a;
+ case STORAGE_RCONST:
+ if (type_dealias(ctx, a)->storage == STORAGE_RUNE) {
+ lower_flexible(ctx, b, a);
+ return a;
+ }
+ if (!type_is_integer(ctx, a)) {
+ return NULL;
+ }
+ if (max_value(ctx, a) < (uint64_t)b->flexible.max) {
+ return NULL;
+ }
+ lower_flexible(ctx, b, a);
+ return a;
+ default:
+ assert(0); // Invariant
+ }
+}
+
+bool
+tagged_subset_compat(struct context *ctx, const struct type *superset, const struct type *subset)
+{
+ // Note: this implementation depends on the invariant that tagged union
+ // member types are sorted by their type ID.
+ superset = type_dealias(ctx, superset), subset = type_dealias(ctx, subset);
+ if (superset->storage != STORAGE_TAGGED || subset->storage != STORAGE_TAGGED) {
+ return false;
+ }
+ size_t sub_i = 0, super_i = 0;
+ while (sub_i < subset->tagged.len && super_i < superset->tagged.len) {
+ while (super_i < superset->tagged.len) {
+ const struct type *sub_memb = subset->tagged.types[sub_i];
+ const struct type *super_memb = superset->tagged.types[super_i];
+ // XXX: Why do we use the ID here?
+ if (sub_memb->id == super_memb->id) {
+ sub_i++;
+ super_i++;
+ break;
+ }
+ super_i++;
+ }
+ }
+
+ return sub_i >= subset->tagged.len;
+}
+
+static bool
+struct_subtype(struct context *ctx,
+ const struct type *to, const struct type *from) {
+ from = type_dealias(ctx, from);
+ if (from->storage != STORAGE_STRUCT) {
+ return false;
+ }
+ for (struct struct_field *f = from->struct_union.fields;
+ f && f->offset == 0; f = f->next) {
+ return f->type == to
+ || struct_subtype(ctx, to, type_dealias(ctx, f->type));
+ }
+ return false;
+}
+
+bool
+type_is_assignable(struct context *ctx,
+ const struct type *to, const struct type *from)
+{
+ const struct type *to_orig = to, *from_orig = from;
+ if (type_dealias(ctx, to)->storage != STORAGE_TAGGED) {
+ to = type_dealias(ctx, to);
+ from = type_dealias(ctx, from);
+ }
+
+ // error and non-error types are mutually assignable
+ to = strip_error(to);
+ from = strip_error(from);
+ if (to == from && to->storage != STORAGE_VOID) {
+ return true;
+ }
+
+ if (from->storage == STORAGE_INVALID
+ || from->storage == STORAGE_NEVER
+ || from->storage == STORAGE_UNDEFINED) {
+ return true;
+ }
+
+ if (type_is_flexible(from)) {
+ return promote_flexible(ctx, to_orig, from_orig);
+ }
+
+ const struct type *to_secondary, *from_secondary;
+ switch (to->storage) {
+ case STORAGE_FCONST:
+ case STORAGE_ICONST:
+ case STORAGE_RCONST:
+ return promote_flexible(ctx, to_orig, from_orig);
+ case STORAGE_I8:
+ case STORAGE_I16:
+ case STORAGE_I32:
+ case STORAGE_I64:
+ case STORAGE_INT:
+ return type_is_integer(ctx, from)
+ && type_is_signed(ctx, from)
+ && to->size >= from->size;
+ case STORAGE_SIZE:
+ case STORAGE_U8:
+ case STORAGE_U16:
+ case STORAGE_U32:
+ case STORAGE_U64:
+ case STORAGE_UINT:
+ return type_is_integer(ctx, from)
+ && !type_is_signed(ctx, from)
+ && to->size >= from->size;
+ case STORAGE_F64:
+ return type_is_float(ctx, from);
+ case STORAGE_POINTER:
+ to_secondary = to->pointer.referent;
+ to_secondary = strip_error(to_secondary);
+ switch (from->storage) {
+ case STORAGE_NULL:
+ return to->pointer.nullable;
+ case STORAGE_POINTER:
+ from_secondary = from->pointer.referent;
+ from_secondary = strip_error(from_secondary);
+ if (struct_subtype(ctx, to_secondary, from_secondary)) {
+ return true;
+ }
+ switch (to_secondary->storage) {
+ case STORAGE_OPAQUE:
+ break;
+ case STORAGE_ARRAY:
+ if (!type_is_assignable(ctx, to_secondary, from_secondary)) {
+ return false;
+ }
+ break;
+ default:
+ if (to_secondary != from_secondary) {
+ return false;
+ }
+ break;
+ }
+ if (from->pointer.nullable) {
+ return to->pointer.nullable;
+ }
+ return true;
+ default:
+ return false;
+ }
+ assert(0); // Unreachable
+ case STORAGE_ALIAS:
+ assert(to->alias.type);
+ return type_is_assignable(ctx, to->alias.type, from);
+ case STORAGE_VOID:
+ return to == from &&
+ type_is_error(ctx, from_orig) == type_is_error(ctx, to_orig);
+ case STORAGE_SLICE:
+ if (from->storage == STORAGE_POINTER) {
+ from = type_dealias(ctx, from->pointer.referent);
+ if (from->storage != STORAGE_ARRAY) {
+ return false;
+ }
+ }
+ if (from->storage != STORAGE_SLICE
+ && (from->storage != STORAGE_ARRAY
+ || from->array.length == SIZE_UNDEFINED)) {
+ return false;
+ }
+ to_secondary = strip_error(to->array.members);
+ from_secondary = strip_error(from->array.members);
+ if (to_secondary->storage == STORAGE_OPAQUE) {
+ return true;
+ }
+ return to_secondary == from_secondary;
+ case STORAGE_ARRAY:
+ if (from->storage != STORAGE_ARRAY) {
+ return false;
+ }
+ if (from->array.expandable) {
+ return to->array.length != SIZE_UNDEFINED
+ && to->array.length >= from->array.length
+ && to->array.members == from->array.members;
+ } else {
+ return to->array.length == SIZE_UNDEFINED
+ && to->array.members == from->array.members;
+ }
+ case STORAGE_TAGGED:
+ return tagged_select_subtype(ctx, to, from_orig, true) != NULL
+ || tagged_subset_compat(ctx, to, from);
+ // The following types are only assignable from themselves, and are
+ // handled above:
+ case STORAGE_BOOL:
+ case STORAGE_DONE:
+ case STORAGE_ENUM:
+ case STORAGE_F32:
+ case STORAGE_FUNCTION:
+ case STORAGE_NEVER:
+ case STORAGE_NOMEM:
+ case STORAGE_NULL:
+ case STORAGE_OPAQUE:
+ case STORAGE_RUNE:
+ case STORAGE_STRING:
+ case STORAGE_STRUCT:
+ case STORAGE_TUPLE:
+ case STORAGE_UINTPTR:
+ case STORAGE_UNION:
+ case STORAGE_VALIST:
+ return false;
+ case STORAGE_INVALID:
+ case STORAGE_UNDEFINED:
+ return true;
+ case STORAGE_ERROR:
+ assert(0); // Handled above
+ }
+
+ assert(0); // Unreachable
+}
+
+static const struct type *
+is_castable_with_tagged(struct context *ctx,
+ const struct type *to, const struct type *from)
+{
+ if (type_dealias(ctx, from)->storage == STORAGE_TAGGED
+ && type_dealias(ctx, to)->storage == STORAGE_TAGGED) {
+ if (tagged_subset_compat(ctx, to, from) || tagged_subset_compat(ctx, from, to)) {
+ return to;
+ }
+ }
+ if (type_dealias(ctx, to)->storage == STORAGE_TAGGED) {
+ const struct type *subtype = tagged_select_subtype(ctx, to, from, true);
+ if (subtype != NULL) {
+ return subtype;
+ }
+ }
+ if (type_dealias(ctx, from)->storage == STORAGE_TAGGED) {
+ const struct type *subtype = tagged_select_subtype(ctx, from, to, true);
+ if (subtype != NULL) {
+ return subtype;
+ }
+ }
+ return NULL;
+}
+
+const struct type *
+type_is_castable(struct context *ctx, const struct type *to, const struct type *from)
+{
+ if (to->storage == STORAGE_INVALID) {
+ return to;
+ } else if (to->storage == STORAGE_UNDEFINED) {
+ return to;
+ }
+
+ if (type_dealias(ctx, from)->storage == STORAGE_TAGGED
+ || type_dealias(ctx, to)->storage == STORAGE_TAGGED) {
+ return is_castable_with_tagged(ctx, to, from);
+ }
+
+ const struct type *to_orig = to, *from_orig = from;
+ to = type_dealias(ctx, to), from = type_dealias(ctx, from);
+ if (to == from) {
+ return to_orig;
+ }
+
+ to = strip_error(to);
+ from = strip_error(from);
+
+ if (to == from) {
+ return to_orig;
+ }
+
+ if ((!type_is_flexible(from) && from->size == SIZE_UNDEFINED)
+ || (!type_is_flexible(to) && to->size == SIZE_UNDEFINED)) {
+ return NULL;
+ }
+
+ switch (from->storage) {
+ case STORAGE_ICONST:
+ switch (to->storage) {
+ case STORAGE_F32:
+ case STORAGE_F64:
+ lower_flexible(ctx, from, NULL);
+ return to_orig;
+ case STORAGE_RUNE:
+ lower_flexible(ctx, from, &builtin_type_u32);
+ return to_orig;
+ default:
+ return promote_flexible(ctx, from_orig, to_orig);
+ }
+ break;
+ case STORAGE_FCONST:
+ if (type_is_integer(ctx, to)) {
+ lower_flexible(ctx, from, NULL);
+ return to_orig;
+ }
+ // fallthrough
+ case STORAGE_RCONST:
+ return promote_flexible(ctx, from_orig, to_orig);
+ case STORAGE_I8:
+ case STORAGE_I16:
+ case STORAGE_I32:
+ case STORAGE_I64:
+ case STORAGE_INT:
+ case STORAGE_SIZE:
+ case STORAGE_U8:
+ case STORAGE_U16:
+ case STORAGE_U32:
+ case STORAGE_U64:
+ case STORAGE_UINT:
+ return to->storage == STORAGE_ENUM
+ || type_is_numeric(ctx, to)
+ || to->storage == STORAGE_RUNE
+ ? to_orig : NULL;
+ case STORAGE_RUNE:
+ return type_is_integer(ctx, to)
+ ? to_orig : NULL;
+ case STORAGE_ENUM:
+ if (from->alias.type->storage == STORAGE_RUNE) {
+ return to->storage == STORAGE_RUNE ? to_orig : NULL;
+ }
+ return to->storage == STORAGE_ENUM || type_is_integer(ctx, to)
+ ? to_orig : NULL;
+ case STORAGE_F32:
+ case STORAGE_F64:
+ return type_is_numeric(ctx, to)
+ ? to_orig : NULL;
+ case STORAGE_UINTPTR:
+ return to->storage == STORAGE_POINTER
+ || to->storage == STORAGE_NULL
+ || type_is_numeric(ctx, to)
+ || to->storage == STORAGE_ENUM
+ ? to_orig : NULL;
+ case STORAGE_POINTER:
+ return to->storage == STORAGE_POINTER
+ || to->storage == STORAGE_NULL
+ || to->storage == STORAGE_UINTPTR
+ ? to_orig : NULL;
+ case STORAGE_NULL:
+ return to->storage == STORAGE_POINTER
+ || to->storage == STORAGE_UINTPTR
+ ? to_orig : NULL;
+ case STORAGE_SLICE:
+ return to->storage == STORAGE_SLICE
+ || (to->storage == STORAGE_POINTER
+ && to->pointer.referent->storage == STORAGE_ARRAY)
+ ? to_orig : NULL;
+ case STORAGE_ARRAY:
+ return to->storage == STORAGE_ARRAY
+ || to->storage == STORAGE_SLICE
+ ? to_orig : NULL;
+ // Cannot be cast:
+ case STORAGE_STRING:
+ case STORAGE_BOOL:
+ case STORAGE_VOID:
+ case STORAGE_DONE:
+ case STORAGE_NEVER:
+ case STORAGE_NOMEM:
+ case STORAGE_OPAQUE:
+ case STORAGE_FUNCTION:
+ case STORAGE_TUPLE:
+ case STORAGE_STRUCT:
+ case STORAGE_UNION:
+ case STORAGE_VALIST:
+ return NULL;
+ case STORAGE_INVALID:
+ case STORAGE_TAGGED:
+ case STORAGE_ALIAS:
+ case STORAGE_ERROR:
+ case STORAGE_UNDEFINED:
+ assert(0); // Handled above
+ }
+
+ assert(0); // Unreachable
+}
+
+void
+builtin_types_init(enum arch target)
+{
+ switch (target) {
+ case AARCH64:
+ builtin_type_f64.align = 8;
+ builtin_type_int.size = 4;
+ builtin_type_int.align = 4;
+ builtin_type_uint.size = 4;
+ builtin_type_uint.align = 4;
+ builtin_type_uintptr.size = 8;
+ builtin_type_uintptr.align = 8;
+ builtin_type_i64.align = 8;
+ builtin_type_u64.align = 8;
+ builtin_type_null.size = 8;
+ builtin_type_null.align = 8;
+ builtin_type_size.size = 8;
+ builtin_type_size.align = 8;
+ builtin_type_str.size = 24;
+ builtin_type_str.align = 8;
+ builtin_type_valist.size = 32;
+ builtin_type_valist.align = 8;
+ break;
+ case PPC64LE:
+ builtin_type_f64.align = 8;
+ builtin_type_int.size = 4;
+ builtin_type_int.align = 4;
+ builtin_type_uint.size = 4;
+ builtin_type_uint.align = 4;
+ builtin_type_uintptr.size = 8;
+ builtin_type_uintptr.align = 8;
+ builtin_type_i64.align = 8;
+ builtin_type_u64.align = 8;
+ builtin_type_null.size = 8;
+ builtin_type_null.align = 8;
+ builtin_type_size.size = 8;
+ builtin_type_size.align = 8;
+ builtin_type_str.size = 24;
+ builtin_type_str.align = 8;
+ builtin_type_valist.size = 8;
+ builtin_type_valist.align = 8;
+ break;
+ case RISCV64:
+ builtin_type_f64.align = 8;
+ builtin_type_int.size = 4;
+ builtin_type_int.align = 4;
+ builtin_type_uint.size = 4;
+ builtin_type_uint.align = 4;
+ builtin_type_uintptr.size = 8;
+ builtin_type_uintptr.align = 8;
+ builtin_type_i64.align = 8;
+ builtin_type_u64.align = 8;
+ builtin_type_null.size = 8;
+ builtin_type_null.align = 8;
+ builtin_type_size.size = 8;
+ builtin_type_size.align = 8;
+ builtin_type_str.size = 24;
+ builtin_type_str.align = 8;
+ builtin_type_valist.size = 8;
+ builtin_type_valist.align = 8;
+ break;
+ case X86_64:
+ builtin_type_f64.align = 8;
+ builtin_type_int.size = 4;
+ builtin_type_int.align = 4;
+ builtin_type_uint.size = 4;
+ builtin_type_uint.align = 4;
+ builtin_type_uintptr.size = 8;
+ builtin_type_uintptr.align = 8;
+ builtin_type_i64.align = 8;
+ builtin_type_u64.align = 8;
+ builtin_type_null.size = 8;
+ builtin_type_null.align = 8;
+ builtin_type_size.size = 8;
+ builtin_type_size.align = 8;
+ builtin_type_str.size = 24;
+ builtin_type_str.align = 8;
+ builtin_type_valist.size = 24;
+ builtin_type_valist.align = 8;
+ break;
+ }
+ struct type *builtins[] = {
+ &builtin_type_bool, &builtin_type_invalid, &builtin_type_f32,
+ &builtin_type_f64, &builtin_type_i8, &builtin_type_i16,
+ &builtin_type_i32, &builtin_type_i64, &builtin_type_int,
+ &builtin_type_u8, &builtin_type_u16, &builtin_type_u32,
+ &builtin_type_u64, &builtin_type_uint, &builtin_type_uintptr,
+ &builtin_type_null, &builtin_type_rune, &builtin_type_size,
+ &builtin_type_never, &builtin_type_done, &builtin_type_nomem,
+ &builtin_type_str, &builtin_type_valist,
+ };
+ for (size_t i = 0; i < sizeof(builtins) / sizeof(builtins[0]); ++i) {
+ builtins[i]->id = type_hash(builtins[i]);
+ }
+}
+
+// Built-in type singletons
+struct type builtin_type_bool = {
+ .storage = STORAGE_BOOL,
+ .size = 1,
+ .align = 1,
+},
+builtin_type_invalid = {
+ .storage = STORAGE_INVALID,
+ .size = 0,
+ .align = 0,
+},
+builtin_type_f32 = {
+ .storage = STORAGE_F32,
+ .size = 4,
+ .align = 4,
+},
+builtin_type_f64 = {
+ .storage = STORAGE_F64,
+ .size = 8,
+},
+builtin_type_i8 = {
+ .storage = STORAGE_I8,
+ .size = 1,
+ .align = 1,
+},
+builtin_type_i16 = {
+ .storage = STORAGE_I16,
+ .size = 2,
+ .align = 2,
+},
+builtin_type_i32 = {
+ .storage = STORAGE_I32,
+ .size = 4,
+ .align = 4,
+},
+builtin_type_i64 = {
+ .storage = STORAGE_I64,
+ .size = 8,
+},
+builtin_type_int = {
+ .storage = STORAGE_INT,
+},
+builtin_type_never = {
+ .storage = STORAGE_NEVER,
+ .size = SIZE_UNDEFINED,
+ .align = ALIGN_UNDEFINED,
+},
+builtin_type_nomem = {
+ .storage = STORAGE_NOMEM,
+ .size = 0,
+ .align = 0,
+},
+builtin_type_opaque = {
+ .storage = STORAGE_OPAQUE,
+ .size = SIZE_UNDEFINED,
+ .align = ALIGN_UNDEFINED,
+},
+builtin_type_u8 = {
+ .storage = STORAGE_U8,
+ .size = 1,
+ .align = 1,
+},
+builtin_type_u16 = {
+ .storage = STORAGE_U16,
+ .size = 2,
+ .align = 2,
+},
+builtin_type_u32 = {
+ .storage = STORAGE_U32,
+ .size = 4,
+ .align = 4,
+},
+builtin_type_u64 = {
+ .storage = STORAGE_U64,
+ .size = 8,
+},
+builtin_type_uint = {
+ .storage = STORAGE_UINT,
+},
+builtin_type_uintptr = {
+ .storage = STORAGE_UINTPTR,
+},
+builtin_type_null = {
+ .storage = STORAGE_NULL,
+},
+builtin_type_rune = {
+ .storage = STORAGE_RUNE,
+ .size = 4,
+ .align = 4,
+},
+builtin_type_size = {
+ .storage = STORAGE_SIZE,
+},
+builtin_type_void = {
+ .storage = STORAGE_VOID,
+ .size = 0,
+ .align = 0,
+},
+builtin_type_done = {
+ .storage = STORAGE_DONE,
+ .size = 0,
+ .align = 0,
+},
+builtin_type_str = {
+ .storage = STORAGE_STRING,
+},
+builtin_type_valist = {
+ .storage = STORAGE_VALIST,
+},
+builtin_type_undefined = {
+ .storage = STORAGE_UNDEFINED,
+ .align = ALIGN_UNDEFINED,
+ .size = SIZE_UNDEFINED,
+};