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, +};