├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── abc_hierarchy.py ├── all_tests.py ├── booleq.py ├── booleq_test.py ├── builtins ├── StringIO.pytd ├── __builtin__.pytd ├── _sre.pytd ├── _struct.pytd ├── _warnings.pytd ├── _weakref.pytd ├── array.pytd ├── codecs.pytd ├── errno.pytd ├── fcntl.pytd ├── gc.pytd ├── itertools.pytd ├── marshal.pytd ├── os.pytd ├── posix.pytd ├── pwd.pytd ├── select.pytd ├── signal.pytd ├── strop.pytd ├── sys.pytd └── warnings.pytd ├── checker.py ├── checker_classes_test.py ├── checker_generics_test.py ├── checker_interface_test.py ├── checker_overloading_test.py ├── checker_test.py ├── checker_union_test.py ├── demo.py ├── examples ├── StringIO.py ├── __init__.py ├── email.py ├── email.pytd └── pytree.py ├── inference_test.py ├── optimize.py ├── optimize_test.py ├── parse ├── __init__.py ├── ast.py ├── ast_test.py ├── builtins.py ├── builtins_test.py ├── decorate.py ├── node.py ├── node_test.py ├── parser.py ├── parser_test.py ├── typed_tuple.py ├── typing.py ├── utils.py ├── utils_test.py ├── visitors.py └── visitors_test.py ├── pytd.py ├── pytd_test.py ├── setup.py ├── slots.py ├── slots_test.py ├── tests ├── __init__.py ├── classes.py ├── classes.pytd ├── generics.py ├── generics.pytd ├── interface.py ├── interface.pytd ├── overloading.py ├── overloading.pytd ├── simple.py ├── simple.pytd ├── union.py └── union.pytd ├── type_match.py ├── type_match_test.py ├── utils.py └── utils_test.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Google Inc. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | ========================================================================= 16 | The following is a copy of http://www.apache.org/licenses/LICENSE-2.0.txt 17 | ========================================================================= 18 | 19 | 20 | Apache License 21 | Version 2.0, January 2004 22 | http://www.apache.org/licenses/ 23 | 24 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 25 | 26 | 1. Definitions. 27 | 28 | "License" shall mean the terms and conditions for use, reproduction, 29 | and distribution as defined by Sections 1 through 9 of this document. 30 | 31 | "Licensor" shall mean the copyright owner or entity authorized by 32 | the copyright owner that is granting the License. 33 | 34 | "Legal Entity" shall mean the union of the acting entity and all 35 | other entities that control, are controlled by, or are under common 36 | control with that entity. For the purposes of this definition, 37 | "control" means (i) the power, direct or indirect, to cause the 38 | direction or management of such entity, whether by contract or 39 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 40 | outstanding shares, or (iii) beneficial ownership of such entity. 41 | 42 | "You" (or "Your") shall mean an individual or Legal Entity 43 | exercising permissions granted by this License. 44 | 45 | "Source" form shall mean the preferred form for making modifications, 46 | including but not limited to software source code, documentation 47 | source, and configuration files. 48 | 49 | "Object" form shall mean any form resulting from mechanical 50 | transformation or translation of a Source form, including but 51 | not limited to compiled object code, generated documentation, 52 | and conversions to other media types. 53 | 54 | "Work" shall mean the work of authorship, whether in Source or 55 | Object form, made available under the License, as indicated by a 56 | copyright notice that is included in or attached to the work 57 | (an example is provided in the Appendix below). 58 | 59 | "Derivative Works" shall mean any work, whether in Source or Object 60 | form, that is based on (or derived from) the Work and for which the 61 | editorial revisions, annotations, elaborations, or other modifications 62 | represent, as a whole, an original work of authorship. For the purposes 63 | of this License, Derivative Works shall not include works that remain 64 | separable from, or merely link (or bind by name) to the interfaces of, 65 | the Work and Derivative Works thereof. 66 | 67 | "Contribution" shall mean any work of authorship, including 68 | the original version of the Work and any modifications or additions 69 | to that Work or Derivative Works thereof, that is intentionally 70 | submitted to Licensor for inclusion in the Work by the copyright owner 71 | or by an individual or Legal Entity authorized to submit on behalf of 72 | the copyright owner. For the purposes of this definition, "submitted" 73 | means any form of electronic, verbal, or written communication sent 74 | to the Licensor or its representatives, including but not limited to 75 | communication on electronic mailing lists, source code control systems, 76 | and issue tracking systems that are managed by, or on behalf of, the 77 | Licensor for the purpose of discussing and improving the Work, but 78 | excluding communication that is conspicuously marked or otherwise 79 | designated in writing by the copyright owner as "Not a Contribution." 80 | 81 | "Contributor" shall mean Licensor and any individual or Legal Entity 82 | on behalf of whom a Contribution has been received by Licensor and 83 | subsequently incorporated within the Work. 84 | 85 | 2. Grant of Copyright License. Subject to the terms and conditions of 86 | this License, each Contributor hereby grants to You a perpetual, 87 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 88 | copyright license to reproduce, prepare Derivative Works of, 89 | publicly display, publicly perform, sublicense, and distribute the 90 | Work and such Derivative Works in Source or Object form. 91 | 92 | 3. Grant of Patent License. Subject to the terms and conditions of 93 | this License, each Contributor hereby grants to You a perpetual, 94 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 95 | (except as stated in this section) patent license to make, have made, 96 | use, offer to sell, sell, import, and otherwise transfer the Work, 97 | where such license applies only to those patent claims licensable 98 | by such Contributor that are necessarily infringed by their 99 | Contribution(s) alone or by combination of their Contribution(s) 100 | with the Work to which such Contribution(s) was submitted. If You 101 | institute patent litigation against any entity (including a 102 | cross-claim or counterclaim in a lawsuit) alleging that the Work 103 | or a Contribution incorporated within the Work constitutes direct 104 | or contributory patent infringement, then any patent licenses 105 | granted to You under this License for that Work shall terminate 106 | as of the date such litigation is filed. 107 | 108 | 4. Redistribution. You may reproduce and distribute copies of the 109 | Work or Derivative Works thereof in any medium, with or without 110 | modifications, and in Source or Object form, provided that You 111 | meet the following conditions: 112 | 113 | (a) You must give any other recipients of the Work or 114 | Derivative Works a copy of this License; and 115 | 116 | (b) You must cause any modified files to carry prominent notices 117 | stating that You changed the files; and 118 | 119 | (c) You must retain, in the Source form of any Derivative Works 120 | that You distribute, all copyright, patent, trademark, and 121 | attribution notices from the Source form of the Work, 122 | excluding those notices that do not pertain to any part of 123 | the Derivative Works; and 124 | 125 | (d) If the Work includes a "NOTICE" text file as part of its 126 | distribution, then any Derivative Works that You distribute must 127 | include a readable copy of the attribution notices contained 128 | within such NOTICE file, excluding those notices that do not 129 | pertain to any part of the Derivative Works, in at least one 130 | of the following places: within a NOTICE text file distributed 131 | as part of the Derivative Works; within the Source form or 132 | documentation, if provided along with the Derivative Works; or, 133 | within a display generated by the Derivative Works, if and 134 | wherever such third-party notices normally appear. The contents 135 | of the NOTICE file are for informational purposes only and 136 | do not modify the License. You may add Your own attribution 137 | notices within Derivative Works that You distribute, alongside 138 | or as an addendum to the NOTICE text from the Work, provided 139 | that such additional attribution notices cannot be construed 140 | as modifying the License. 141 | 142 | You may add Your own copyright statement to Your modifications and 143 | may provide additional or different license terms and conditions 144 | for use, reproduction, or distribution of Your modifications, or 145 | for any such Derivative Works as a whole, provided Your use, 146 | reproduction, and distribution of the Work otherwise complies with 147 | the conditions stated in this License. 148 | 149 | 5. Submission of Contributions. Unless You explicitly state otherwise, 150 | any Contribution intentionally submitted for inclusion in the Work 151 | by You to the Licensor shall be under the terms and conditions of 152 | this License, without any additional terms or conditions. 153 | Notwithstanding the above, nothing herein shall supersede or modify 154 | the terms of any separate license agreement you may have executed 155 | with Licensor regarding such Contributions. 156 | 157 | 6. Trademarks. This License does not grant permission to use the trade 158 | names, trademarks, service marks, or product names of the Licensor, 159 | except as required for reasonable and customary use in describing the 160 | origin of the Work and reproducing the content of the NOTICE file. 161 | 162 | 7. Disclaimer of Warranty. Unless required by applicable law or 163 | agreed to in writing, Licensor provides the Work (and each 164 | Contributor provides its Contributions) on an "AS IS" BASIS, 165 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 166 | implied, including, without limitation, any warranties or conditions 167 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 168 | PARTICULAR PURPOSE. You are solely responsible for determining the 169 | appropriateness of using or redistributing the Work and assume any 170 | risks associated with Your exercise of permissions under this License. 171 | 172 | 8. Limitation of Liability. In no event and under no legal theory, 173 | whether in tort (including negligence), contract, or otherwise, 174 | unless required by applicable law (such as deliberate and grossly 175 | negligent acts) or agreed to in writing, shall any Contributor be 176 | liable to You for damages, including any direct, indirect, special, 177 | incidental, or consequential damages of any character arising as a 178 | result of this License or out of the use or inability to use the 179 | Work (including but not limited to damages for loss of goodwill, 180 | work stoppage, computer failure or malfunction, or any and all 181 | other commercial damages or losses), even if such Contributor 182 | has been advised of the possibility of such damages. 183 | 184 | 9. Accepting Warranty or Additional Liability. While redistributing 185 | the Work or Derivative Works thereof, You may choose to offer, 186 | and charge a fee for, acceptance of support, warranty, indemnity, 187 | or other liability obligations and/or rights consistent with this 188 | License. However, in accepting such obligations, You may act only 189 | on Your own behalf and on Your sole responsibility, not on behalf 190 | of any other Contributor, and only if You agree to indemnify, 191 | defend, and hold each Contributor harmless for any liability 192 | incurred by, or claims asserted against, such Contributor by reason 193 | of your accepting any such warranty or additional liability. 194 | 195 | END OF TERMS AND CONDITIONS 196 | 197 | APPENDIX: How to apply the Apache License to your work. 198 | 199 | To apply the Apache License to your work, attach the following 200 | boilerplate notice, with the fields enclosed by brackets "[]" 201 | replaced with your own identifying information. (Don't include 202 | the brackets!) The text should be enclosed in the appropriate 203 | comment syntax for the file format. We also recommend that a 204 | file or class name and description of purpose be included on the 205 | same "printed page" as the copyright notice for easier 206 | identification within third-party archives. 207 | 208 | Copyright [yyyy] [name of copyright owner] 209 | 210 | Licensed under the Apache License, Version 2.0 (the "License"); 211 | you may not use this file except in compliance with the License. 212 | You may obtain a copy of the License at 213 | 214 | http://www.apache.org/licenses/LICENSE-2.0 215 | 216 | Unless required by applicable law or agreed to in writing, software 217 | distributed under the License is distributed on an "AS IS" BASIS, 218 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 219 | See the License for the specific language governing permissions and 220 | limitations under the License. 221 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Pytypedecl - https://github.com/google/pytypedecl/ 2 | 3 | Pytypedecl consists of a type declaration language for Python and an optional 4 | run-time type checker. This project was started by [Raoul-Gabriel 5 | Urma](http://www.urma.com) under the supervision of Peter Ludemann and Gregory 6 | P. Smith during a summer 2013 internship at Google. 7 | 8 | ## License 9 | Apache 2.0 10 | 11 | ## Motivation 12 | ### Why types declarations? 13 | 14 | Type declarations are useful to document your code. This proposal starts a 15 | conversation with the community to reach a standard for a type declaration 16 | language for Python. 17 | 18 | ### Why runtime type-checking? 19 | Runtime type-checking of optional type declarations is useful for code 20 | maintenance and debugging. Our type-checker is available as a Python package. 21 | You therefore do not need to run external tools to benefit from the 22 | type-checking. 23 | 24 | ## Status 25 | This project is in its infancy -- we intend to make many updates in the next 26 | couple of months. We currently support Python 2.7. Support for Python 3 coming 27 | soon. 28 | 29 | ## Type declaration language 30 | 31 | Types declarations are specified in an external file with the 32 | extension **"pytd"**. For example if you want to provide types for 33 | **"application.py"**, you define the type inside the file 34 | **"application.pytd"**. Examples of type declarations files can be 35 | found in the **/tests/** folder. 36 | 37 | Here’s an example of a type declaration file that mixes several features: 38 | ```python 39 | class Logger: 40 | def log(messages: list, buffer: Readable or Writeable) raises IOException 41 | def log(messages: list) -> None 42 | def setStatus(status: int or str) 43 | ``` 44 | 45 | 46 | The type declaration language currently supports the following features: 47 | 48 | * **Function signatures**: Functions can be given a signature following the 49 | Python 3 function annotation convention. However, we extended it in a number of 50 | ways that would be difficult or clumsy using Python 3's annotations. 51 | 52 | * **Exceptions**: In addition to the return type, you can specify the 53 | exceptions that the function might raise. There is no runtime checking 54 | for this, but exceptions can be useful documentation and an automated 55 | type inferencer could deduce the possible exceptions that a function 56 | might throw. 57 | 58 | * **Overloading**: A function is allowed to have multiple different signatures. 59 | This is not supported in the Python 3 function annotation syntax but is 60 | supported by pytypedecl. 61 | 62 | * **Union types**: It is sometime convenient to indicate that a type can hold 63 | values from a number of different types. Union types allow to express this 64 | idea. For example `int or float` indicates that a value may be an `int` or a 65 | `float`. There is no limit to the number of types in a union. A none-able type 66 | can be seen as the union of a type and None. 67 | (Note: None is a unit type and is a subtype of NoneType. Because there's 68 | only one subtype of NoneType, for type-specification purposes, None and 69 | NoneType are the same.) 70 | 71 | * **Generics**: A type can be parameterised with a set of type arguments, 72 | similarly to Java generics. For example, `generator` describes a generator 73 | that only produces `str`s, `dict` describes a dictionary of keys of 74 | type `str` and values of type `int`. 75 | 76 | ### Coming soon: 77 | * Declaration of type parameters for methods and classes. 78 | * Bounded type parameters 79 | * Support for tags: @classmethod, @staticmethod... 80 | 81 | ## How to get started 82 | ``` 83 | git clone https://github.com/google/pytypedecl.git 84 | python setup.py install 85 | ``` 86 | The package is now installed. You can run an example: 87 | ``` 88 | $ python -B demo.py 89 | ``` 90 | The **-B** flag prevents the generation of **pyc** file. 91 | 92 | You can also run the tests: 93 | ``` 94 | $ python -B all_tests.py 95 | ``` 96 | 97 | Look into the **/examples/** directory to see how the emailer example 98 | works. You need to do two things to type-check your program: 99 | 100 | **1. Create a type declaration file** 101 | 102 | Create a type declaration file that has the name of the Python file you want to 103 | type-check but with the extension .pytd (e.g. email.pytd for email.py) 104 | 105 | **2. Import the checker package** 106 | 107 | Include the following two imports in the Python file that you want to 108 | type-check: 109 | ``` 110 | import sys 111 | import checker 112 | ``` 113 | And the following line after your function and class declarations (before they 114 | are used) 115 | ``` 116 | checker.CheckFromFile(sys.modules[__name__], __file__ + "td") 117 | ``` 118 | That’s it! You can now run your python program and it will be type-checked at 119 | runtime using the type declarations you defined in the **pytd** file. 120 | 121 | ## How to contribute to the project 122 | 123 | * Check out the issue tracker 124 | * Mailing List: https://groups.google.com/forum/#!forum/pytypedecl-dev 125 | * Send us suggestions 126 | * Fork 127 | 128 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/pytypedecl/c7a1bdd9044b00b67e1fc21a48365e424eace0c0/__init__.py -------------------------------------------------------------------------------- /abc_hierarchy.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | """Abstract base class hierarchy for both Python 2 and Python 3.""" 18 | 19 | import collections 20 | 21 | 22 | def Invert(d): 23 | """Invert a dictionary. 24 | 25 | Converts a dictionary (mapping strings to lists of strings) to a dictionary 26 | that maps into the other direction. 27 | 28 | Arguments: 29 | d: Dictionary to be inverted 30 | 31 | Returns: 32 | A dictionary n with the property that if "y in d[x]", then "x in n[y]". 33 | """ 34 | 35 | inverted = collections.defaultdict(list) 36 | for key, value_list in d.items(): 37 | for val in value_list: 38 | inverted[val].append(key) 39 | return inverted 40 | 41 | 42 | # (We specify the below manually, instead of extracting it out of abc.py, 43 | # because we need support for Python 2 as well as Python 3 without having to 44 | # depend on the "host" Python) 45 | 46 | 47 | # class -> list of superclasses 48 | SUPERCLASSES = { 49 | # "mixins" (don't derive from object) 50 | "Hashable": [], 51 | "Iterable": [], 52 | "Sized": [], 53 | "Callable": [], 54 | "Iterator": ["Iterable"], 55 | 56 | # Classes (derive from object). Same for Python 2 and 3. 57 | "Container": ["object"], 58 | "Number": ["object"], 59 | "Complex": ["Number"], 60 | "Real": ["Complex"], 61 | "Rational": ["Real"], 62 | "Integral": ["Rational"], 63 | "Set": ["Sized", "Iterable", "Container"], 64 | "MutableSet": ["Set"], 65 | "Mapping": ["Sized", "Iterable", "Container"], 66 | "MappingView": ["Sized"], 67 | "KeysView": ["MappingView", "Set"], 68 | "ItemsView": ["MappingView", "Set"], 69 | "ValuesView": ["MappingView"], 70 | "MutableMapping": ["Mapping"], 71 | "Sequence": ["Sized", "Iterable", "Container"], 72 | "MutableSequence": ["Sequence"], 73 | 74 | # Builtin types. Python 2 and 3 agree on these: 75 | "set": ["MutableSet"], 76 | "frozenset": ["Set"], 77 | "dict": ["MutableMapping"], 78 | "tuple": ["Sequence"], 79 | "list": ["MutableSequence"], 80 | "complex": ["Complex"], 81 | "float": ["Real"], 82 | "int": ["Integral"], 83 | "bool": ["int"], 84 | 85 | # In Python 3, str is registered directly with Sequence. In Python 2, 86 | # str inherits from basestring, which inherits from Sequence. We simplify 87 | # things by just letting str inherit from Sequence everywhere. 88 | "str": ["Sequence"], 89 | "basestring": ["Sequence"], 90 | "bytes": ["Sequence"], 91 | 92 | # Types that exist in Python 2, but not in Python 3: 93 | "buffer": ["Sequence"], 94 | "long": ["Integral"], 95 | "xrange": ["Sequence"], 96 | "unicode": ["Sequence"], 97 | 98 | # Types that exist in Python 3, but not in Python 2: 99 | "range": ["Sequence"], # "range" is a function, not a type, in Python 2 100 | 101 | # Omitted: "bytearray". It inherits from ByteString (which only exists in 102 | # Python 3) and MutableSequence. The latter exists in Python 2, but 103 | # bytearray isn't registered with it. 104 | 105 | # Python 2 + 3 types that can only be constructed indirectly. 106 | # (See EOL comments for the definition) 107 | "bytearray_iterator": ["Iterator"], # type(iter(bytearray())) 108 | "dict_keys": ["KeysView"], # type({}.viewkeys()) or type({}.keys()). 109 | "dict_items": ["ItemsView"], # type({}.viewitems()) or type({}.items()). 110 | "dict_values": ["ValuesView"], # type({}.viewvalues()) or type({}.values()) 111 | 112 | # Python 2 types that can only be constructed indirectly. 113 | "dictionary-keyiterator": ["Iterator"], # type(iter({}.viewkeys())) 114 | "dictionary-valueiterator": ["Iterator"], # type(iter({}.viewvalues())) 115 | "dictionary-itemiterator": ["Iterator"], # type(iter({}.viewitems())) 116 | "listiterator": ["Iterator"], # type(iter([])) 117 | "listreverseiterator": ["Iterator"], # type(iter(reversed([]))) 118 | "rangeiterator": ["Iterator"], # type(iter(xrange(0))) 119 | "setiterator": ["Iterator"], # type(iter(set())) 120 | "tupleiterator": ["Iterator"], # type(iter(())) 121 | 122 | # Python 3 types that can only be constructed indirectly. 123 | "dict_keyiterator": ["Iterator"], # type(iter({}.keys())) 124 | "dict_valueiterator": ["Iterator"], # type(iter({}.values())) 125 | "dict_itemiterator": ["Iterator"], # type(iter({}.items())) 126 | "list_iterator": ["Iterator"], # type(iter([])) 127 | "list_reverseiterator": ["Iterator"], # type(iter(reversed([]))) 128 | "range_iterator": ["Iterator"], # type(iter(range(0))) 129 | "set_iterator": ["Iterator"], # type(iter(set())) 130 | "tuple_iterator": ["Iterator"], # type(iter(())) 131 | "str_iterator": ["Iterator"], # type(iter("")). Python 2: just "iterator" 132 | "zip_iterator": ["Iterator"], # type(iter(zip())). Python 2: listiterator 133 | "bytes_iterator": ["Iterator"], # type(iter(b'')). Python 2: bytes == str 134 | } 135 | 136 | 137 | def GetSuperClasses(): 138 | """Get a Python type hierarchy mapping. 139 | 140 | This generates a dictionary that can be used to look up the parents of 141 | a type in the abstract base class hierarchy. 142 | 143 | Returns: 144 | A dictionary mapping a type, as string, to a list of base types (also 145 | as strings). E.g. "float" -> ["Real"]. 146 | """ 147 | 148 | return SUPERCLASSES.copy() 149 | 150 | 151 | def GetSubClasses(): 152 | """Get a reverse Python type hierarchy mapping. 153 | 154 | This generates a dictionary that can be used to look up the (known) 155 | subclasses of a type in the abstract base class hierarchy. 156 | 157 | Returns: 158 | A dictionary mapping a type, as string, to a list of direct 159 | subclasses (also as strings). 160 | E.g. "Sized" -> ["Set", "Mapping", "MappingView", "Sequence"]. 161 | """ 162 | 163 | return Invert(GetSuperClasses()) 164 | -------------------------------------------------------------------------------- /all_tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import unittest 19 | import checker_classes_test 20 | import checker_generics_test 21 | import checker_overloading_test 22 | import checker_test 23 | import checker_union_test 24 | from parse import ast_test 25 | 26 | 27 | def suite(): 28 | 29 | # ast tests 30 | # TODO: can this be simplified using test discovery? 31 | 32 | ast_generation = unittest.TestLoader().loadTestsFromTestCase( 33 | ast_test.TestASTGeneration) 34 | tuple_eq = unittest.TestLoader().loadTestsFromTestCase(ast_test.TestTupleEq) 35 | 36 | # checker tests 37 | classes = unittest.TestLoader().loadTestsFromTestCase( 38 | checker_classes_test.TestCheckerClasses) 39 | generics = unittest.TestLoader().loadTestsFromTestCase( 40 | checker_generics_test.TestCheckerGenerics) 41 | overloading = unittest.TestLoader().loadTestsFromTestCase( 42 | checker_overloading_test.TestCheckerOverloading) 43 | simple = unittest.TestLoader().loadTestsFromTestCase(checker_test.TestChecker) 44 | union = unittest.TestLoader().loadTestsFromTestCase( 45 | checker_union_test.TestCheckerUnion) 46 | 47 | all_tests = [ast_generation, tuple_eq, classes, generics, overloading, 48 | simple, union] 49 | 50 | return unittest.TestSuite(all_tests) 51 | 52 | 53 | if __name__ == "__main__": 54 | unittest.TextTestRunner().run(suite()) 55 | -------------------------------------------------------------------------------- /booleq_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | # Copyright 2013 Google Inc. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Tests for booleq.py.""" 17 | 18 | import unittest 19 | 20 | from pytypedecl import booleq 21 | 22 | # pylint: disable=invalid-name 23 | And = booleq.And 24 | Or = booleq.Or 25 | Eq = booleq.Eq 26 | 27 | 28 | # TODO: a coworker wants me to remind him to create more tests for 29 | # booleq.py. 30 | 31 | 32 | class TestBoolEq(unittest.TestCase): 33 | """Test algorithms and datastructures of booleq.py.""" 34 | 35 | def testTrueAndFalse(self): 36 | self.assertNotEqual(booleq.TRUE, booleq.FALSE) 37 | self.assertNotEqual(booleq.FALSE, booleq.TRUE) 38 | self.assertEqual(booleq.TRUE, booleq.TRUE) 39 | self.assertEqual(booleq.FALSE, booleq.FALSE) 40 | 41 | def testEquality(self): 42 | self.assertEqual(booleq.Eq("a", "b"), 43 | booleq.Eq("b", "a")) 44 | self.assertEqual(booleq.Eq("a", "b"), 45 | booleq.Eq("a", "b")) 46 | self.assertNotEqual(booleq.Eq("a", "a"), 47 | booleq.Eq("a", "b")) 48 | self.assertNotEqual(booleq.Eq("b", "a"), 49 | booleq.Eq("b", "b")) 50 | 51 | def testAnd(self): 52 | self.assertEqual(booleq.TRUE, 53 | booleq.And([])) 54 | self.assertEqual(booleq.TRUE, 55 | booleq.And([booleq.TRUE])) 56 | self.assertEqual(booleq.TRUE, 57 | booleq.And([booleq.TRUE, booleq.TRUE])) 58 | self.assertEqual(booleq.FALSE, 59 | booleq.And([booleq.TRUE, booleq.FALSE])) 60 | self.assertEqual(booleq.Eq("a", "b"), 61 | booleq.And([booleq.Eq("a", "b"), 62 | booleq.TRUE])) 63 | self.assertEqual(booleq.FALSE, 64 | booleq.And([booleq.Eq("a", "b"), 65 | booleq.FALSE])) 66 | 67 | def testOr(self): 68 | self.assertEqual(booleq.FALSE, 69 | booleq.Or([])) 70 | self.assertEqual(booleq.TRUE, 71 | booleq.Or([booleq.TRUE])) 72 | self.assertEqual(booleq.TRUE, 73 | booleq.Or([booleq.TRUE, booleq.TRUE])) 74 | self.assertEqual(booleq.TRUE, 75 | booleq.Or([booleq.TRUE, booleq.FALSE])) 76 | self.assertEqual(booleq.Eq("a", "b"), 77 | booleq.Or([booleq.Eq("a", "b"), 78 | booleq.FALSE])) 79 | self.assertEqual(booleq.TRUE, 80 | booleq.Or([booleq.Eq("a", "b"), 81 | booleq.TRUE])) 82 | 83 | def testNestedEquals(self): 84 | eq1 = booleq.Eq("a", "u") 85 | eq2 = booleq.Eq("b", "v") 86 | eq3 = booleq.Eq("c", "w") 87 | eq4 = booleq.Eq("d", "x") 88 | nested = Or([And([eq1, eq2]), And([eq3, eq4])]) 89 | self.assertEqual(nested, nested) 90 | 91 | def testOrder(self): 92 | eq1 = booleq.Eq("a", "b") 93 | eq2 = booleq.Eq("b", "c") 94 | self.assertEqual(booleq.Or([eq1, eq2]), 95 | booleq.Or([eq2, eq1])) 96 | self.assertEqual(booleq.And([eq1, eq2]), 97 | booleq.And([eq2, eq1])) 98 | 99 | def testHash(self): 100 | eq1 = booleq.Eq("a", "b") 101 | eq2 = booleq.Eq("b", "c") 102 | eq3 = booleq.Eq("c", "d") 103 | self.assertEqual(hash(booleq.Eq("x", "y")), 104 | hash(booleq.Eq("y", "x"))) 105 | self.assertEqual(hash(booleq.Or([eq1, eq2, eq3])), 106 | hash(booleq.Or([eq2, eq3, eq1]))) 107 | self.assertEqual(hash(booleq.And([eq1, eq2, eq3])), 108 | hash(booleq.And([eq2, eq3, eq1]))) 109 | 110 | def testPivots(self): 111 | # x == 0 || x == 1 112 | equation = Or([Eq("x", "0"), Eq("x", "1")]) 113 | self.assertItemsEqual(["0", "1"], equation.extract_pivots()["x"]) 114 | 115 | # x == 0 && x == 0 116 | equation = And([Eq("x", "0"), Eq("x", "0")]) 117 | self.assertItemsEqual(["0"], equation.extract_pivots()["x"]) 118 | 119 | # x == 0 && (x == 0 || x == 1) 120 | equation = And([Eq("x", "0"), Or([Eq("x", "0"), Eq("x", "1")])]) 121 | self.assertItemsEqual(["0"], equation.extract_pivots()["x"]) 122 | 123 | # x == 0 || x == 0 124 | equation = And([Eq("x", "0"), Eq("x", "0")]) 125 | self.assertItemsEqual(["0"], equation.extract_pivots()["x"]) 126 | 127 | def testSimplify(self): 128 | # x == 0 || x == 1 with x in {0} 129 | equation = Or([Eq("x", "0"), Eq("x", "1")]) 130 | values = {"x": ["0"]} 131 | self.assertEquals(Eq("x", "0"), equation.simplify(values)) 132 | 133 | # x == 0 || x == 1 with x in {0} 134 | equation = Or([Eq("x", "0"), Eq("x", "1")]) 135 | values = {"x": ["0", "1"]} 136 | self.assertEquals(equation, equation.simplify(values)) 137 | 138 | # x == 0 with x in {1} 139 | equation = Eq("x", "0") 140 | values = {"x": ["1"]} 141 | self.assertEquals(booleq.FALSE, equation.simplify(values)) 142 | 143 | # x == 0 with x in {0} 144 | equation = Eq("x", "0") 145 | values = {"x": ["0"]} 146 | self.assertEquals(equation, equation.simplify(values)) 147 | 148 | # x == 0 && y == 0 with x in {1}, y in {1} 149 | equation = Or([Eq("x", "0"), Eq("y", "1")]) 150 | values = {"x": ["1"], "y": ["1"]} 151 | self.assertEquals(Eq("y", "1"), equation.simplify(values)) 152 | 153 | # x == 0 && y == 0 with x in {0}, y in {1} 154 | equation = Or([Eq("x", "0"), Eq("y", "1")]) 155 | values = {"x": ["0"], "y": ["1"]} 156 | self.assertEquals(equation, equation.simplify(values)) 157 | 158 | # x == 0 && x == 0 with x in {0} 159 | equation = And([Eq("x", "0"), Eq("x", "0")]) 160 | values = {"x": ["0"]} 161 | self.assertEquals(Eq("x", "0"), equation.simplify(values)) 162 | 163 | # x == y with x in {0, 1} and y in {1, 2} 164 | equation = Eq("x", "y") 165 | values = {"x": ["0", "1"], "y": ["1", "2"]} 166 | self.assertEquals(And([Eq("x", "1"), Eq("y", "1")]), 167 | equation.simplify(values)) 168 | 169 | def _MakeSolver(self, variables=("x", "y"), values=("1", "2")): 170 | solver = booleq.Solver() 171 | for variable in variables: 172 | solver.register_variable(variable) 173 | for value in values: 174 | solver.register_value(str(value)) 175 | return solver 176 | 177 | def testImplication(self): 178 | solver = self._MakeSolver() 179 | solver.implies(Eq("x", "1"), Eq("y", "1")) 180 | solver.implies(Eq("x", "2"), booleq.FALSE) # not Eq("x", "2") 181 | self.assertDictEqual(solver.solve(), 182 | {"x": set(["1"]), 183 | "y": set(["1"])}) 184 | 185 | def testGroundTruth(self): 186 | solver = self._MakeSolver() 187 | solver.implies(Eq("x", "1"), Eq("y", "1")) 188 | solver.always_true(Eq("x", "1")) 189 | self.assertDictEqual(solver.solve(), 190 | {"x": set(["1"]), 191 | "y": set(["1"])}) 192 | 193 | def testFilter(self): 194 | solver = self._MakeSolver(["x", "y"], ["1", "2", "3"]) 195 | solver.implies(Eq("x", "1"), booleq.TRUE) 196 | solver.implies(Eq("x", "2"), booleq.FALSE) 197 | solver.implies(Eq("x", "3"), booleq.FALSE) 198 | solver.implies(Eq("y", "1"), Or([Eq("x", "1"), Eq("x", "2"), Eq("x", "3")])) 199 | solver.implies(Eq("y", "2"), Or([Eq("x", "2"), Eq("x", "3")])) 200 | solver.implies(Eq("y", "3"), Or([Eq("x", "2")])) 201 | self.assertDictEqual(solver.solve(), 202 | {"x": set(["1"]), 203 | "y": set(["1"])}) 204 | 205 | def testSolveAnd(self): 206 | solver = self._MakeSolver(["x", "y", "z"], ["1", "2", "3"]) 207 | solver.always_true(Eq("x", "1")) 208 | solver.implies(Eq("y", "1"), And([Eq("x", "1"), Eq("z", "1")])) 209 | solver.implies(Eq("x", "1"), And([Eq("y", "1"), Eq("z", "1")])) 210 | solver.implies(Eq("z", "1"), And([Eq("x", "1"), Eq("y", "1")])) 211 | self.assertDictEqual(solver.solve(), 212 | {"x": set(["1"]), 213 | "y": set(["1"]), 214 | "z": set(["1"])}) 215 | 216 | if __name__ == "__main__": 217 | unittest.main() 218 | -------------------------------------------------------------------------------- /builtins/StringIO.pytd: -------------------------------------------------------------------------------- 1 | EINVAL: int 2 | __all__: list 3 | __builtins__: dict 4 | __doc__: str 5 | __file__: str 6 | __name__: str 7 | __package__: NoneType 8 | 9 | def _complain_ifclosed(closed) -> NoneType 10 | def test() -> NoneType 11 | 12 | class StringIO: 13 | def close(self) -> NoneType 14 | def flush(self) -> NoneType 15 | def getvalue(self) -> str 16 | def isatty(self) -> bool 17 | def read(self, ...) -> str 18 | def readline(self, ...) -> str 19 | def readlines(self, ...) -> list 20 | def seek(self, pos, mode) -> NoneType 21 | def tell(self) -> int 22 | def truncate(self, ...) -> NoneType 23 | def write(self, s) -> NoneType 24 | def writelines(self, iterable) -> NoneType 25 | -------------------------------------------------------------------------------- /builtins/_sre.pytd: -------------------------------------------------------------------------------- 1 | CODESIZE: long or int or str 2 | MAGIC: long or int or str 3 | MAXREPEAT: long or int or str 4 | copyright: long or int or str 5 | 6 | def compile(...) -> SRE_Pattern raises OverflowError 7 | def getcodesize() -> int 8 | def getlower(...) -> int 9 | 10 | class SRE_Match: 11 | def __copy__() -> ? raises TypeError # TODO: fix return type 12 | def __deepcopy__(...) -> ? raises TypeError # TODO: fix return type 13 | def end(...) -> int raises IndexError 14 | def expand(...) -> str # TODO: unicode? 15 | def group(...) -> tuple 16 | def groupdict(...) -> dict 17 | def groups(...) -> tuple 18 | def span(...) -> tuple raises IndexError 19 | def start(...) -> int raises IndexError 20 | 21 | class SRE_Pattern: 22 | def __copy__() -> ? raises TypeError # TODO: fix return type 23 | def __deepcopy__(...) -> ? raises TypeError # TODO: fix return type 24 | def findall(source, ...) -> list 25 | def finditer(...) -> `callable-iterator` 26 | def match(pattern, ...) -> NoneType or SRE_Match 27 | def scanner(...) -> SRE_Scanner 28 | def search(pattern, ...) -> NoneType or SRE_Match 29 | def split(source, ...) -> list 30 | def sub(repl, string, ...) -> tuple 31 | def subn(repl, string, ...) -> tuple 32 | 33 | class SRE_Scanner: 34 | def match() -> NoneType or SRE_Match 35 | def search() -> NoneType or SRE_Match 36 | -------------------------------------------------------------------------------- /builtins/_struct.pytd: -------------------------------------------------------------------------------- 1 | def _clearcache() -> NoneType 2 | def calcsize(...) -> int 3 | def pack(...) -> str raises TypeError 4 | def pack_into(...) -> NoneType raises TypeError 5 | def unpack(...) -> tuple 6 | def unpack_from(...) -> tuple raises TypeError 7 | 8 | class Struct: 9 | def __sizeof__() -> long 10 | def pack(...) -> str 11 | def pack_into(...) -> NoneType 12 | def unpack(...) -> tuple 13 | def unpack_from(...) -> tuple 14 | -------------------------------------------------------------------------------- /builtins/_warnings.pytd: -------------------------------------------------------------------------------- 1 | def warn(message, ...) -> NoneType 2 | def warn_explicit(message, category, filename, lineno: int, ...) -> NoneType -------------------------------------------------------------------------------- /builtins/_weakref.pytd: -------------------------------------------------------------------------------- 1 | def getweakrefcount(...) -> int 2 | def getweakrefs(...) -> list 3 | def proxy(...) -> ? # TODO: fix return type 4 | -------------------------------------------------------------------------------- /builtins/array.pytd: -------------------------------------------------------------------------------- 1 | # TODO: parameterize by type? 2 | class array: 3 | def __copy__() -> ? 4 | def __deepcopy__(...) -> ? 5 | def __reduce__() -> tuple raises AttributeError 6 | def __sizeof__() -> long 7 | def append(...) -> NoneType 8 | def buffer_info() -> tuple 9 | def byteswap() -> NoneType raises RuntimeError 10 | def count(...) -> int 11 | def extend(...) -> NoneType 12 | def fromfile(...) -> NoneType raises EOFError, IOError, MemoryError, TypeError 13 | def fromlist(...) -> NoneType raises MemoryError, TypeError 14 | def fromstring(...) -> NoneType raises MemoryError, ValueError 15 | def fromunicode(...) -> NoneType raises MemoryError, ValueError 16 | def index(...) -> int raises ValueError 17 | def insert(...) -> NoneType 18 | def pop(...) -> ? raises IndexError 19 | def read(...) -> NoneType raises DeprecationWarning 20 | def remove(...) -> NoneType raises ValueError 21 | def reverse() -> NoneType 22 | def tofile(...) -> NoneType raises IOError, TypeError 23 | def tolist() -> list 24 | def tostring() -> str raises MemoryError 25 | def tounicode() -> unicode raises ValueError 26 | def write(...) -> NoneType raises DeprecationWarning 27 | 28 | class arrayiterator: 29 | pass 30 | -------------------------------------------------------------------------------- /builtins/codecs.pytd: -------------------------------------------------------------------------------- 1 | BOM: str 2 | BOM32_BE: str 3 | BOM32_LE: str 4 | BOM64_BE: str 5 | BOM64_LE: str 6 | BOM_BE: str 7 | BOM_LE: str 8 | BOM_UTF16: str 9 | BOM_UTF16_BE: str 10 | BOM_UTF16_LE: str 11 | BOM_UTF32: str 12 | BOM_UTF32_BE: str 13 | BOM_UTF32_LE: str 14 | BOM_UTF8: str 15 | 16 | # TODO: check def's with "-> ?" 17 | class BufferedIncrementalDecoder: 18 | def __init__(self, ...) -> NoneType 19 | def _buffer_decode(self, input, errors, final) -> ? raises NotImplementedError 20 | def decode(self, input, final) -> ? raises NotImplementedError 21 | def getstate(self) -> tuple 22 | def reset(self) -> NoneType 23 | def setstate(self, state) -> NoneType 24 | 25 | class BufferedIncrementalEncoder: 26 | def __init__(self, ...) -> NoneType 27 | def _buffer_encode(self, input, errors, final) -> ? raises NotImplementedError 28 | def encode(self, input, final) -> ? raises NotImplementedError 29 | def getstate(self) -> int 30 | def reset(self) -> NoneType 31 | def setstate(self, state) -> NoneType 32 | 33 | class Codec: 34 | def __init__(self, ...) -> NoneType 35 | def decode(self, input, errors) -> ? raises NotImplementedError 36 | def encode(self, input, errors) -> ? raises NotImplementedError 37 | -------------------------------------------------------------------------------- /builtins/errno.pytd: -------------------------------------------------------------------------------- 1 | errorcode: dict -------------------------------------------------------------------------------- /builtins/fcntl.pytd: -------------------------------------------------------------------------------- 1 | def fcntl(...) -> str or int raises IOError, ValueError 2 | def flock(...) -> NoneType raises IOError 3 | def ioctl(...) -> str or int raises IOError, ValueError 4 | def lockf(...) -> NoneType raises IOError, ValueError -------------------------------------------------------------------------------- /builtins/gc.pytd: -------------------------------------------------------------------------------- 1 | def collect(...) -> int raises ValueError 2 | def disable() -> NoneType 3 | def enable() -> NoneType 4 | def get_count() -> tuple 5 | def get_debug() -> int 6 | def get_objects() -> list 7 | def get_referents(...) -> list 8 | def get_referrers(...) -> list 9 | def get_threshold() -> tuple 10 | def is_tracked(...) -> bool 11 | def isenabled() -> bool 12 | def set_debug(a: int) -> NoneType 13 | def set_threshold(a: int, ...) -> NoneType -------------------------------------------------------------------------------- /builtins/itertools.pytd: -------------------------------------------------------------------------------- 1 | def tee(a, ...) -> tuple raises ValueError 2 | 3 | class _grouper: 4 | pass 5 | 6 | class chain: 7 | def from_iterable(...) -> ? 8 | 9 | class combinations: 10 | pass 11 | 12 | class combinations_with_replacement: 13 | pass 14 | 15 | class compress: 16 | pass 17 | 18 | class count: 19 | def __reduce__() -> tuple 20 | 21 | class cycle: 22 | pass 23 | 24 | class dropwhile: 25 | pass 26 | 27 | class groupby: 28 | pass 29 | 30 | class ifilter: 31 | pass 32 | 33 | class ifilterfalse: 34 | pass 35 | 36 | class imap: 37 | pass 38 | 39 | class islice: 40 | pass 41 | 42 | class izip: 43 | pass 44 | 45 | class izip_longest: 46 | pass 47 | 48 | class permutations: 49 | pass 50 | 51 | class product: 52 | pass 53 | 54 | class repeat: 55 | def __length_hint__() -> int raises TypeError 56 | 57 | class starmap: 58 | pass 59 | 60 | class takewhile: 61 | pass 62 | 63 | class tee_dataobject: 64 | pass 65 | -------------------------------------------------------------------------------- /builtins/marshal.pytd: -------------------------------------------------------------------------------- 1 | def dump(...) -> NoneType raises TypeError 2 | def dumps(...) -> unicode # TODO: "-> str"? 3 | def load(...) -> ? raises TypeError 4 | def loads(...) -> ? 5 | -------------------------------------------------------------------------------- /builtins/os.pytd: -------------------------------------------------------------------------------- 1 | def WCOREDUMP(...) -> bool 2 | def WEXITSTATUS(...) -> int 3 | def WIFCONTINUED(...) -> bool 4 | def WIFEXITED(...) -> bool 5 | def WIFSIGNALED(...) -> bool 6 | def WIFSTOPPED(...) -> bool 7 | def WSTOPSIG(...) -> int 8 | def WTERMSIG(...) -> int 9 | def _exit(...) -> NoneType # TODO: raises? 10 | def abort() -> NoneType # TODO: raises? 11 | def access(...) -> bool 12 | def chdir(...) -> NoneType 13 | def chmod(...) -> NoneType 14 | def chown(...) -> NoneType 15 | def chroot(...) -> NoneType 16 | def close(...) -> NoneType 17 | def closerange(...) -> NoneType 18 | def confstr(...) -> str or NoneType 19 | def ctermid() -> str 20 | def dup(...) -> int 21 | def dup2(...) -> NoneType 22 | def execv(...) -> ? raises MemoryError, TypeError, ValueError 23 | def execve(...) -> ? raises MemoryError, TypeError 24 | def fchdir(...) -> NoneType 25 | def fchmod(...) -> NoneType 26 | def fchown(...) -> NoneType 27 | def fdatasync(...) -> NoneType 28 | def fdopen(...) -> file raises MemoryError 29 | def fork() -> int raises RuntimeError 30 | def forkpty() -> tuple raises RuntimeError 31 | def fpathconf(...) -> int 32 | def fstat(...) -> tuple 33 | def fstatvfs(...) -> tuple 34 | def fsync(...) -> NoneType 35 | def ftruncate(...) -> NoneType 36 | def getcwd() -> str 37 | def getcwdu() -> unicode 38 | def getegid() -> long or int 39 | def geteuid() -> long or int 40 | def getgid() -> long or int 41 | def getgroups() -> list 42 | def getloadavg() -> tuple raises OSError 43 | def getlogin() -> str raises OSError 44 | def getpgid(...) -> int 45 | def getpgrp() -> int 46 | def getpid() -> int 47 | def getppid() -> int 48 | def getresgid() -> tuple 49 | def getresuid() -> tuple 50 | def getsid(...) -> int 51 | def getuid() -> long or int 52 | def initgroups(...) -> NoneType raises OSError 53 | def isatty(...) -> bool 54 | def kill(...) -> NoneType 55 | def killpg(...) -> NoneType 56 | def lchown(...) -> NoneType 57 | def link(...) -> NoneType 58 | def listdir(...) -> list 59 | def lseek(...) -> int 60 | def lstat(...) -> tuple 61 | def major(...) -> int 62 | def makedev(...) -> int 63 | def minor(...) -> int 64 | def mkdir(...) -> NoneType 65 | def mkfifo(...) -> NoneType 66 | def mknod(...) -> NoneType 67 | def nice(...) -> int 68 | def open(...) -> int 69 | def openpty() -> tuple 70 | def pathconf(...) -> int 71 | def pipe() -> tuple 72 | def popen(...) -> file 73 | def putenv(...) -> NoneType raises MemoryError 74 | def read(...) -> str 75 | def readlink(...) -> str 76 | def remove(...) -> NoneType 77 | def rename(...) -> NoneType 78 | def rmdir(...) -> NoneType 79 | def setegid(...) -> NoneType 80 | def seteuid(...) -> NoneType 81 | def setgid(...) -> NoneType 82 | def setgroups(...) -> NoneType raises TypeError, ValueError 83 | def setpgid(...) -> NoneType 84 | def setpgrp() -> NoneType 85 | def setregid(...) -> NoneType 86 | def setresgid(...) -> NoneType 87 | def setresuid(...) -> NoneType 88 | def setreuid(...) -> NoneType 89 | def setsid() -> NoneType 90 | def setuid(...) -> NoneType 91 | def stat(...) -> tuple 92 | def stat_float_times(...) -> bool or NoneType 93 | def statvfs(...) -> tuple 94 | def strerror(...) -> str raises ValueError 95 | def symlink(...) -> NoneType 96 | def sysconf(...) -> int 97 | def system(...) -> int 98 | def tcgetpgrp(...) -> int 99 | def tcsetpgrp(...) -> NoneType 100 | def times() -> tuple 101 | def tmpfile() -> file raises DeprecationWarning 102 | def ttyname(...) -> str 103 | def umask(...) -> int 104 | def uname() -> tuple 105 | def unlink(...) -> NoneType 106 | def unsetenv(...) -> NoneType 107 | def urandom(...) -> str raises ValueError 108 | def utime(...) -> NoneType raises TypeError 109 | def wait() -> tuple 110 | def wait3(...) -> tuple 111 | def wait4(...) -> tuple 112 | def waitpid(...) -> tuple 113 | def write(...) -> int 114 | -------------------------------------------------------------------------------- /builtins/posix.pytd: -------------------------------------------------------------------------------- 1 | def WCOREDUMP(...) -> bool 2 | def WEXITSTATUS(...) -> int 3 | def WIFCONTINUED(...) -> bool 4 | def WIFEXITED(...) -> bool 5 | def WIFSIGNALED(...) -> bool 6 | def WIFSTOPPED(...) -> bool 7 | def WSTOPSIG(...) -> int 8 | def WTERMSIG(...) -> int 9 | def _exit(...) -> NoneType # TODO: raises? 10 | def abort() -> NoneType # TODO: raises? 11 | def access(...) -> bool 12 | def chdir(...) -> NoneType 13 | def chmod(...) -> NoneType 14 | def chown(...) -> NoneType 15 | def chroot(...) -> NoneType 16 | def close(...) -> NoneType 17 | def closerange(...) -> NoneType 18 | def confstr(...) -> str or NoneType 19 | def ctermid() -> str 20 | def dup(...) -> int 21 | def dup2(...) -> NoneType 22 | def execv(...) -> ? raises MemoryError, TypeError, ValueError 23 | def execve(...) -> ? raises MemoryError, TypeError 24 | def fchdir(...) -> NoneType 25 | def fchmod(...) -> NoneType 26 | def fchown(...) -> NoneType 27 | def fdatasync(...) -> NoneType 28 | def fdopen(...) -> file raises MemoryError 29 | def fork() -> int raises RuntimeError 30 | def forkpty() -> tuple raises RuntimeError 31 | def fpathconf(...) -> int 32 | def fstat(...) -> tuple 33 | def fstatvfs(...) -> tuple 34 | def fsync(...) -> NoneType 35 | def ftruncate(...) -> NoneType 36 | def getcwd() -> str 37 | def getcwdu() -> unicode 38 | def getegid() -> long or int 39 | def geteuid() -> long or int 40 | def getgid() -> long or int 41 | def getgroups() -> list 42 | def getloadavg() -> tuple raises OSError 43 | def getlogin() -> str raises OSError 44 | def getpgid(...) -> int 45 | def getpgrp() -> int 46 | def getpid() -> int 47 | def getppid() -> int 48 | def getresgid() -> tuple 49 | def getresuid() -> tuple 50 | def getsid(...) -> int 51 | def getuid() -> long or int 52 | def initgroups(...) -> NoneType raises OSError 53 | def isatty(...) -> bool 54 | def kill(...) -> NoneType 55 | def killpg(...) -> NoneType 56 | def lchown(...) -> NoneType 57 | def link(...) -> NoneType 58 | def listdir(...) -> list 59 | def lseek(...) -> int 60 | def lstat(...) -> tuple 61 | def major(...) -> int 62 | def makedev(...) -> int 63 | def minor(...) -> int 64 | def mkdir(...) -> NoneType 65 | def mkfifo(...) -> NoneType 66 | def mknod(...) -> NoneType 67 | def nice(...) -> int 68 | def open(...) -> int 69 | def openpty() -> tuple 70 | def pathconf(...) -> int 71 | def pipe() -> tuple 72 | def popen(...) -> file 73 | def putenv(...) -> NoneType raises MemoryError 74 | def read(...) -> str 75 | def readlink(...) -> str 76 | def remove(...) -> NoneType 77 | def rename(...) -> NoneType 78 | def rmdir(...) -> NoneType 79 | def setegid(...) -> NoneType 80 | def seteuid(...) -> NoneType 81 | def setgid(...) -> NoneType 82 | def setgroups(...) -> NoneType raises TypeError, ValueError 83 | def setpgid(...) -> NoneType 84 | def setpgrp() -> NoneType 85 | def setregid(...) -> NoneType 86 | def setresgid(...) -> NoneType 87 | def setresuid(...) -> NoneType 88 | def setreuid(...) -> NoneType 89 | def setsid() -> NoneType 90 | def setuid(...) -> NoneType 91 | def stat(...) -> tuple 92 | def stat_float_times(...) -> bool or NoneType 93 | def statvfs(...) -> tuple 94 | def strerror(...) -> str raises ValueError 95 | def symlink(...) -> NoneType 96 | def sysconf(...) -> int 97 | def system(...) -> int 98 | def tcgetpgrp(...) -> int 99 | def tcsetpgrp(...) -> NoneType 100 | def times() -> tuple 101 | def tmpfile() -> file raises DeprecationWarning 102 | def ttyname(...) -> str 103 | def umask(...) -> int 104 | def uname() -> tuple 105 | def unlink(...) -> NoneType 106 | def unsetenv(...) -> NoneType 107 | def urandom(...) -> str raises ValueError 108 | def utime(...) -> NoneType raises TypeError 109 | def wait() -> tuple 110 | def wait3(...) -> tuple 111 | def wait4(...) -> tuple 112 | def waitpid(...) -> tuple 113 | def write(...) -> int 114 | -------------------------------------------------------------------------------- /builtins/pwd.pytd: -------------------------------------------------------------------------------- 1 | def getpwall() -> list 2 | def getpwnam(a: str) -> tuple raises KeyError 3 | def getpwuid(a) -> tuple raises KeyError, OverflowError -------------------------------------------------------------------------------- /builtins/select.pytd: -------------------------------------------------------------------------------- 1 | def poll() -> `poll-type` 2 | def select(...) -> tuple raises OverflowError, TypeError 3 | 4 | class epoll: 5 | def close() -> NoneType raises IOError 6 | def fileno() -> int 7 | def fromfd(a: int) -> epoll 8 | def modify(fd, eventmask: int) -> NoneType 9 | def poll(...) -> list raises IOError, MemoryError, OverflowError, ValueError 10 | def register(fd, ...) -> NoneType 11 | def unregister(fd) -> NoneType 12 | 13 | class `poll-type`: 14 | pass 15 | -------------------------------------------------------------------------------- /builtins/signal.pytd: -------------------------------------------------------------------------------- 1 | ITIMER_PROF: int or long 2 | ITIMER_REAL: int or long 3 | ITIMER_VIRTUAL: int or long 4 | ItimerError: object 5 | NSIG: int or long 6 | SIGABRT: int or long 7 | SIGALRM: int or long 8 | SIGBUS: int or long 9 | SIGCHLD: int or long 10 | SIGCLD: int or long 11 | SIGCONT: int or long 12 | SIGFPE: int or long 13 | SIGHUP: int or long 14 | SIGILL: int or long 15 | SIGINT: int or long 16 | SIGIO: int or long 17 | SIGIOT: int or long 18 | SIGKILL: int or long 19 | SIGPIPE: int or long 20 | SIGPOLL: int or long 21 | SIGPROF: int or long 22 | SIGPWR: int or long 23 | SIGQUIT: int or long 24 | SIGRTMAX: int or long 25 | SIGRTMIN: int or long 26 | SIGSEGV: int or long 27 | SIGSTOP: int or long 28 | SIGSYS: int or long 29 | SIGTERM: int or long 30 | SIGTRAP: int or long 31 | SIGTSTP: int or long 32 | SIGTTIN: int or long 33 | SIGTTOU: int or long 34 | SIGURG: int or long 35 | SIGUSR1: int or long 36 | SIGUSR2: int or long 37 | SIGVTALRM: int or long 38 | SIGWINCH: int or long 39 | SIGXCPU: int or long 40 | SIGXFSZ: int or long 41 | SIG_DFL: int or long 42 | SIG_IGN: int or long 43 | 44 | def alarm(a: int) -> int 45 | # def default_int_handler(...) raises KeyboardInterrupt # TODO: is this defined? 46 | def getitimer(a: int) -> tuple 47 | def getsignal(a: int) -> NoneType raises ValueError 48 | def pause() -> NoneType 49 | def set_wakeup_fd(a: int) -> long raises ValueError 50 | def setitimer(a: int, b: float, ...) -> tuple 51 | def siginterrupt(a: int, b: int) -> NoneType raises RuntimeError, ValueError 52 | def signal(a: int, b) -> NoneType raises RuntimeError, TypeError, ValueError 53 | -------------------------------------------------------------------------------- /builtins/strop.pytd: -------------------------------------------------------------------------------- 1 | def rfind(...) -> int raises DeprecationWarning 2 | def join(...) -> object or str raises TypeError, OverflowError, DeprecationWarning 3 | def capitalize(...) -> str raises DeprecationWarning 4 | def swapcase(...) -> str raises DeprecationWarning 5 | def rstrip(...) -> str raises DeprecationWarning 6 | def upper(...) -> str raises DeprecationWarning 7 | def maketrans(...) -> str raises ValueError 8 | def find(...) -> int raises DeprecationWarning 9 | def lower(...) -> str raises DeprecationWarning 10 | def replace(...) -> str raises MemoryError, ValueError, DeprecationWarning 11 | def translate(...) -> str raises ValueError, DeprecationWarning 12 | def count(...) -> int raises DeprecationWarning 13 | def strip(...) -> str raises DeprecationWarning 14 | def split(...) -> list raises ValueError, DeprecationWarning 15 | def atof(...) -> float raises ValueError, DeprecationWarning 16 | def atoi(...) -> int raises ValueError, DeprecationWarning 17 | def expandtabs(...) -> str raises OverflowError, ValueError, DeprecationWarning 18 | def splitfields(...) -> list raises ValueError, DeprecationWarning 19 | def atol(...) -> long raises ValueError, DeprecationWarning 20 | def joinfields(...) -> object or str raises TypeError, OverflowError, DeprecationWarning 21 | def lstrip(...) -> str raises DeprecationWarning 22 | -------------------------------------------------------------------------------- /builtins/sys.pytd: -------------------------------------------------------------------------------- 1 | __displayhook__: object 2 | __excepthook__: object 3 | __stderr__: file 4 | __stdin__: file 5 | __stdout__: file 6 | _mercurial: tuple 7 | api_version: int 8 | argv: list 9 | builtin_module_names: list 10 | byteorder: str 11 | copyright: str 12 | dont_write_bytecode: bool 13 | exec_prefix: str 14 | executable: str 15 | flags: tuple 16 | float_info: object 17 | float_repr_style: str 18 | hexversion: int 19 | long_info: object 20 | maxint: int 21 | maxsize: int 22 | maxunicode: int 23 | path: list 24 | platform: str 25 | prefix: str 26 | py3kwarning: bool 27 | stderr: file 28 | stdin: file 29 | stdout: file 30 | subversion: tuple 31 | version: str 32 | version_info: tuple 33 | warnoptions: object 34 | 35 | def _clear_type_cache() -> NoneType 36 | def _current_frames() -> ? # TODO: fix return type 37 | def _getframe(...) -> ? raises ValueError # TODO: fix return type 38 | def call_tracing(a, b) -> ? # TODO: fix return type 39 | def displayhook(...) -> NoneType raises RuntimeError 40 | def exc_clear() -> NoneType raises DeprecationWarning 41 | def exc_info() -> tuple 42 | def excepthook(...) -> NoneType 43 | def exit(...) -> NoneType raises SystemExit 44 | def getcheckinterval() -> int 45 | def getdefaultencoding() -> str 46 | def getdlopenflags() -> int 47 | def getfilesystemencoding() -> str or NoneType 48 | def getprofile() -> NoneType 49 | def getrecursionlimit() -> int 50 | def getrefcount(...) -> int 51 | def getsizeof(object, ...) -> int raises TypeError 52 | def gettrace() -> NoneType 53 | def setcheckinterval(a: int) -> NoneType 54 | def setdefaultencoding(a: str) -> NoneType 55 | def setdlopenflags(a: int) -> NoneType 56 | def setprofile(...) -> NoneType 57 | def setrecursionlimit(a: int) -> NoneType raises ValueError 58 | def settrace(...) -> NoneType 59 | -------------------------------------------------------------------------------- /builtins/warnings.pytd: -------------------------------------------------------------------------------- 1 | __all__: list 2 | __builtins__: dict 3 | __doc__: str 4 | __file__: str 5 | __name__: str 6 | __package__: NoneType 7 | default_action: str 8 | defaultaction: str 9 | filters: list 10 | linecache: module 11 | once_registry: dict 12 | onceregistry: dict 13 | sys: module 14 | types: module 15 | 16 | def _getaction(action) -> str 17 | def _processoptions(args) -> NoneType 18 | def _setoption(arg) -> NoneType 19 | def resetwarnings() -> NoneType 20 | def warn(x, ...) -> NoneType 21 | def warnpy3k(message, category, stacklevel) -> NoneType 22 | 23 | class WarningMessage: 24 | pass 25 | 26 | class _OptionError: 27 | def __unicode__(self, ...) -> unicode 28 | 29 | class catch_warnings: 30 | pass 31 | -------------------------------------------------------------------------------- /checker_classes_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import unittest 19 | from pytypedecl import checker 20 | from tests import classes 21 | 22 | 23 | class TestCheckerClasses(unittest.TestCase): 24 | 25 | def testEmailer(self): 26 | emailer = classes.Emailer() 27 | page_email = "nobody@example.com" 28 | expected_msg = "sending email to " + page_email 29 | self.assertEquals(expected_msg, emailer.SendEmail(page_email)) 30 | 31 | # NOTE: We only check that we get the correct type of exception, but don't 32 | # verify the attributes of the exception (error message string etc.) 33 | # In theory, we might miss things (complaining about the wrong type, or 34 | # for the wrong reason), but tests are way too flaky if we depend on the 35 | # exact format of an exception message string. 36 | 37 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 38 | emailer.MakeAnnouncement("nobody@example.com") 39 | 40 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 41 | classes.Emailer.GetServerInfo("25") 42 | 43 | def testUtils(self): 44 | utils = classes.Utils() 45 | self.assertEquals("aaa", utils.Repeat("a", 3.0)) 46 | 47 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 48 | utils.Repeat("a", "3") 49 | 50 | expected = checker.OverloadingTypeErrorMsg("Repeat") 51 | 52 | [actual] = context.exception.args[0] 53 | self.assertEquals(expected, actual) 54 | 55 | def testComparators(self): 56 | 57 | self.assertEquals(True, classes.Comparators.IsGreater(20, 10)) 58 | 59 | # call using with class name 60 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 61 | classes.Comparators.IsGreater("20", 10) 62 | 63 | # call using instance of comparators 64 | comparators = classes.Comparators() 65 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 66 | comparators.IsGreater(20, "10") 67 | 68 | 69 | if __name__ == "__main__": 70 | unittest.main() 71 | -------------------------------------------------------------------------------- /checker_generics_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import unittest 19 | from pytypedecl import checker 20 | from tests import generics 21 | 22 | 23 | class TestCheckerGenerics(unittest.TestCase): 24 | 25 | def testSimpleList(self): 26 | """Type checking of a list of int.""" 27 | 28 | # should work with no exceptions 29 | self.assertEquals(3, generics.Length([1, 2, 3])) 30 | 31 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 32 | generics.Length(["42", 1]) 33 | 34 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 35 | generics.Length(["abc", 1, 3]) 36 | 37 | def testUserContainerClass(self): 38 | """Type checking of a container class.""" 39 | 40 | self.assertEquals(1, generics.UnwrapBox(generics.Box(1))) 41 | 42 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 43 | generics.UnwrapBox(generics.Box("hello")) 44 | 45 | def testDict(self): 46 | """Type checking of built-in dict.""" 47 | cache = {"Albert": 1, "Greg": 2, "Peter": 3} 48 | 49 | self.assertEquals(1, generics.FindInCache(cache, "Albert")) 50 | 51 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 52 | generics.FindInCache(cache, 9999) 53 | 54 | def testGenSimple(self): 55 | """Type checking of typed generator.""" 56 | 57 | self.assertEquals([1, 2], generics.ConvertGenToList( 58 | e for e in [1, 2])) 59 | 60 | gen = generics._BadGen() 61 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 62 | generics.ConvertGenToList(gen) 63 | 64 | def testSameGenAsTwoArgs(self): 65 | """Passing same generator twice.""" 66 | 67 | gen = (e for e in [1, 2, 3, 4, 5, 6]) 68 | self.assertEquals([], generics.ConsumeDoubleGenerator(gen, gen)) 69 | 70 | gen_broken = (e for e in [1, 2, 3, 4, 5, "6"]) 71 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 72 | generics.ConsumeDoubleGenerator(gen_broken, gen_broken) 73 | 74 | 75 | if __name__ == "__main__": 76 | unittest.main() 77 | -------------------------------------------------------------------------------- /checker_interface_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import unittest 19 | from pytypedecl import checker 20 | from pytypedecl import pytd 21 | from tests import interface 22 | 23 | 24 | class TestCheckerInterface(unittest.TestCase): 25 | 26 | def testSimpleInterfaceAsArg(self): 27 | """Function expecting a type matching an Interface.""" 28 | 29 | self.assertEquals("Hello", interface.ReadStuff(interface.FakeReadable())) 30 | 31 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 32 | interface.ReadStuff(interface.FakeOpenable()) 33 | 34 | expected_p = checker.ParamTypeErrorMsg( 35 | "ReadStuff", 36 | "r", 37 | interface.FakeOpenable, 38 | pytd.StructType([pytd.MinimalFunction("Open"), 39 | pytd.MinimalFunction("Read"), 40 | pytd.MinimalFunction("Close")])) 41 | 42 | expected_e = checker.ExceptionTypeErrorMsg("ReadStuff", 43 | AttributeError, 44 | ()) 45 | [actual_p, actual_e] = context.exception.args[0] 46 | self.assertEquals(expected_p, actual_p) 47 | self.assertEquals(expected_e, actual_e) 48 | 49 | # TODO: reinstate this test, probably with Interface 50 | def testReturnInterface(self): 51 | """Function returning an object matching an Interface. 52 | """ 53 | 54 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 55 | interface.GetWritable() 56 | 57 | expected_r = checker.ReturnTypeErrorMsg( 58 | "GetWritable", 59 | interface.NoGoodWritable, 60 | pytd.StructType([pytd.MinimalFunction("Open"), 61 | pytd.MinimalFunction("Write"), 62 | pytd.MinimalFunction("Close")])) 63 | 64 | [actual_r] = context.exception.args[0] 65 | self.assertEquals(expected_r, actual_r) 66 | 67 | 68 | if __name__ == "__main__": 69 | unittest.main() 70 | -------------------------------------------------------------------------------- /checker_overloading_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import unittest 19 | from pytypedecl import checker 20 | from tests import overloading 21 | from tests import simple 22 | 23 | 24 | class TestCheckerOverloading(unittest.TestCase): 25 | 26 | def testOverloadedArgsSimpleNoError(self): 27 | """Type checking a function that has two overloaded sigs.""" 28 | # this should work normally 29 | self.assertEquals(42, overloading.Bar(1)) 30 | self.assertEquals(42, overloading.Bar("a")) 31 | 32 | def testOverloadedArgsSimpleError(self): 33 | """Type checking a function that has two overloaded sigs. 34 | """ 35 | expected = checker.OverloadingTypeErrorMsg("Bar") 36 | 37 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 38 | overloading.Bar(1.0) 39 | 40 | [actual] = context.exception.args[0] 41 | self.assertEquals(expected, actual) 42 | 43 | def testFibonnaciNoError(self): 44 | """Type checking of fib. 45 | """ 46 | # should work no errors 47 | self.assertEquals(8, overloading.Fib(5)) 48 | self.assertEquals(5, overloading.Fib(4.0)) 49 | 50 | def testFibonnaciError(self): 51 | """Type checking of fib. 52 | """ 53 | expected = checker.OverloadingTypeErrorMsg("Fib") 54 | 55 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 56 | overloading.Fib("foo") 57 | 58 | [actual] = context.exception.args[0] 59 | self.assertEquals(expected, actual) 60 | 61 | def testMultiOverloadingNoError(self): 62 | """Type checking of multiple argument sigs. 63 | """ 64 | # this should work normally 65 | self.assertEquals(42, overloading.MultiOverload(42)) 66 | self.assertEquals(42.0, overloading.MultiOverload(42.0)) 67 | self.assertEquals("42", overloading.MultiOverload("42")) 68 | self.assertEquals([42], overloading.MultiOverload([42])) 69 | 70 | def testMultiOverloadingError(self): 71 | """Type checking of multiple argument sigs. 72 | """ 73 | expected = checker.OverloadingTypeErrorMsg("MultiOverload") 74 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 75 | overloading.MultiOverload({}) # dict not supported 76 | 77 | [actual] = context.exception.args[0] 78 | self.assertEquals(expected, actual) 79 | 80 | def testOverloadedExceptions(self): 81 | """Overloaded function with exceptions. 82 | """ 83 | with self.assertRaises(simple.WrongException): 84 | overloading.ExceptionOverload() 85 | 86 | if __name__ == "___main__": 87 | unittest.main() 88 | -------------------------------------------------------------------------------- /checker_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import types 19 | import unittest 20 | from pytypedecl import checker 21 | from tests import simple 22 | 23 | 24 | class TestChecker(unittest.TestCase): 25 | 26 | def testSimpleArgTypeNoError(self): 27 | """Type checking of function with single argument.""" 28 | # there should be no exceptions thrown 29 | result = simple.IntToInt(2) 30 | self.assertEquals(42, result) 31 | 32 | def testSimpleArgTypeError(self): 33 | """Type checking of function with single argument. 34 | """ 35 | # there should be a type error exception 36 | 37 | expected = checker.ParamTypeErrorMsg("IntToInt", "i", str, int) 38 | 39 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 40 | simple.IntToInt("test") 41 | 42 | [actual] = context.exception.args[0] 43 | self.assertEquals(expected, actual) 44 | 45 | def testMultiArgTypeError(self): 46 | """Type checking of function with multiple argument. 47 | """ 48 | # there should be a type error exception 49 | 50 | expected_a = checker.ParamTypeErrorMsg("MultiArgs", "a", str, int) 51 | expected_c = checker.ParamTypeErrorMsg("MultiArgs", "c", int, dict) 52 | expected_d = checker.ParamTypeErrorMsg("MultiArgs", "d", dict, str) 53 | 54 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 55 | simple.MultiArgs("test", 1, 2, {}) 56 | 57 | a, c, d = context.exception.args[0] 58 | self.assertEquals(expected_a, a) 59 | self.assertEquals(expected_c, c) 60 | self.assertEquals(expected_d, d) 61 | 62 | def testReturnTypeNoError(self): 63 | """Type checking of return type of function. 64 | """ 65 | res = simple.GoodRet() 66 | self.assertEquals(type(res), int) 67 | 68 | def testReturnTypeError(self): 69 | """Type checking of return type of function. 70 | """ 71 | 72 | expected = checker.ReturnTypeErrorMsg("BadRet", str, int) 73 | 74 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 75 | simple.BadRet() 76 | 77 | [actual] = context.exception.args[0] 78 | self.assertEquals(expected, actual) 79 | 80 | def testReturnNone(self): 81 | """Type checking of return type of function with None. 82 | """ 83 | 84 | expected = checker.ReturnTypeErrorMsg("NoneRet", list, types.NoneType) 85 | 86 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 87 | simple.NoneRet() 88 | 89 | [actual] = context.exception.args[0] 90 | self.assertEquals(expected, actual) 91 | 92 | def testReturnAClassType(self): 93 | """Type checking of return type of a function returning a class type. 94 | """ 95 | 96 | expected = checker.ReturnTypeErrorMsg("AppleRet", 97 | simple.Banana, 98 | simple.Apple) 99 | 100 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 101 | simple.AppleRet() 102 | 103 | [actual] = context.exception.args[0] 104 | self.assertEquals(expected, actual) 105 | 106 | def testExceptionCorrect(self): 107 | """Type checking of exception: the correct exception is thrown. 108 | """ 109 | with self.assertRaises(simple.FooException): 110 | simple.FooFail() 111 | 112 | def testExceptionListCorrect(self): 113 | """Type checking of exception: one correct exception from a list. 114 | """ 115 | with self.assertRaises(simple.WrongException): 116 | simple.WrongFail() 117 | 118 | def testExceptionIncorrect(self): 119 | """Type checking of exception: incorrect exception thrown. 120 | """ 121 | 122 | expected = checker.ExceptionTypeErrorMsg( 123 | "BadFail", 124 | simple.BadException, 125 | (simple.FooException, simple.WrongException)) 126 | 127 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 128 | simple.BadFail() 129 | 130 | [actual] = context.exception.args[0] 131 | self.assertEquals(expected, actual) 132 | 133 | def testMultiTypeErrors(self): 134 | """Type checking of params and exceptions. 135 | """ 136 | 137 | expected_a = checker.ParamTypeErrorMsg("MultiFail", 138 | "a", 139 | simple.Banana, 140 | simple.Apple) 141 | expected_e = checker.ExceptionTypeErrorMsg( 142 | "MultiFail", 143 | simple.BadException, 144 | (simple.FooException,)) 145 | 146 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 147 | simple.MultiFail(simple.Banana()) 148 | 149 | a, e = context.exception.args[0] 150 | self.assertEquals(expected_a, a) 151 | self.assertEquals(expected_e, e) 152 | 153 | def testOptionalTypeParam(self): 154 | """Type checking with params without type. 155 | """ 156 | self.assertEquals(1, simple.MultiArgsNoType(1, 2, 3, "4", [])) 157 | 158 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 159 | simple.MultiArgsNoType(1, 2, 3, 4, 5) 160 | 161 | expected_p = checker.ParamTypeErrorMsg("MultiArgsNoType", 162 | "d", 163 | int, 164 | str) 165 | 166 | [actual] = context.exception.args[0] 167 | self.assertEquals(expected_p, actual) 168 | 169 | 170 | if __name__ == "__main__": 171 | unittest.main() 172 | -------------------------------------------------------------------------------- /checker_union_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import unittest 19 | from pytypedecl import checker 20 | from pytypedecl import pytd 21 | from tests import simple 22 | from tests import union 23 | 24 | 25 | class TestCheckerUnion(unittest.TestCase): 26 | 27 | def testSimpleArgNoneAble(self): 28 | """Type checking of function with none-able argument.""" 29 | # should work with no exceptions 30 | self.assertEquals(0, union.StrToInt(None)) 31 | self.assertEquals(10, union.StrToInt("10")) 32 | 33 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 34 | union.StrToInt(10) # can only pass str? so this should be an error 35 | 36 | expected = checker.ParamTypeErrorMsg("StrToInt", 37 | "s", 38 | int, 39 | pytd.UnionType([str, type(None)])) 40 | 41 | [actual] = context.exception.args[0] 42 | self.assertEquals(expected, actual) 43 | 44 | def testNoneAbleAdd(self): 45 | """Type checking of function with None-able args, return and overloading. 46 | """ 47 | 48 | self.assertEquals(None, union.Add(None, 4)) 49 | self.assertEquals(None, union.Add(10.0, None)) 50 | self.assertEquals(10, union.Add(5, 5)) 51 | self.assertEquals(10.0, union.Add(5.0, 5.0)) 52 | 53 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 54 | union.Add([1], None) # list not in signature 55 | 56 | expected = checker.OverloadingTypeErrorMsg("Add") 57 | 58 | [actual] = context.exception.args[0] 59 | self.assertEquals(expected, actual) 60 | 61 | def testUnionSimple(self): 62 | """Type checking of function with union args. 63 | """ 64 | self.assertEquals(42.0, union.IntOrFloat(1, 2.0)) 65 | self.assertEquals(42.0, union.IntOrFloat(1.0, 2)) 66 | 67 | def testUnionError(self): 68 | """Type checking of function with union args (error). 69 | """ 70 | 71 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 72 | union.IntOrFloat("1", 2) 73 | 74 | expected = checker.ParamTypeErrorMsg("IntOrFloat", 75 | "a", 76 | str, 77 | pytd.UnionType([int, float])) 78 | 79 | [actual] = context.exception.args[0] 80 | self.assertEquals(expected, actual) 81 | 82 | def testUnionNone(self): 83 | """Type checking of function with None union. 84 | """ 85 | self.assertEquals(3, union.IntOrNone(3)) 86 | self.assertEquals(None, union.IntOrNone(None)) 87 | 88 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 89 | union.IntOrNone("error") 90 | 91 | expected_param = checker.ParamTypeErrorMsg("IntOrNone", 92 | "a", 93 | str, 94 | pytd.UnionType([int, 95 | type(None)])) 96 | 97 | expected_ret = checker.ReturnTypeErrorMsg("IntOrNone", 98 | str, 99 | pytd.UnionType([int, 100 | type(None)])) 101 | 102 | [actual_param, actual_ret] = context.exception.args[0] 103 | self.assertEquals(expected_param, actual_param) 104 | self.assertEquals(expected_ret, actual_ret) 105 | 106 | def testUnionWithClassTypes(self): 107 | """Type checking of function with union and class types. 108 | """ 109 | 110 | self.assertEquals(None, union.AppleOrBananaOrOrange(simple.Apple())) 111 | self.assertEquals(None, union.AppleOrBananaOrOrange(simple.Banana())) 112 | self.assertEquals(None, union.AppleOrBananaOrOrange(simple.Orange())) 113 | 114 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 115 | union.AppleOrBananaOrOrange(42) 116 | 117 | expected = checker.ParamTypeErrorMsg("AppleOrBananaOrOrange", 118 | "f", 119 | int, 120 | pytd.UnionType([simple.Apple, 121 | simple.Banana, 122 | simple.Orange])) 123 | 124 | [actual] = context.exception.args[0] 125 | self.assertEquals(expected, actual) 126 | 127 | def testUnionInReturnOK(self): 128 | """Typechecking fct with union in return type. 129 | """ 130 | self.assertEquals([42], union.UnionReturn()) 131 | 132 | def testUnionInReturnError(self): 133 | """Typechecking fct with union in return type (error). 134 | """ 135 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 136 | union.UnionReturnError() 137 | 138 | expected = checker.ReturnTypeErrorMsg("UnionReturnError", 139 | tuple, 140 | pytd.UnionType([int, list])) 141 | 142 | [actual] = context.exception.args[0] 143 | self.assertEquals(expected, actual) 144 | 145 | def testIntersectionSimple(self): 146 | """Typechecking fct with intersection types. 147 | """ 148 | self.assertEquals("cool", union.DoSomeIOStuff(union.File())) 149 | 150 | def testIntersectionError(self): 151 | """Typechecking fct with intersection types (error). 152 | """ 153 | with self.assertRaises(checker.CheckTypeAnnotationError) as context: 154 | union.DoSomeIOStuff(union.Readable()) # we want Readable & Writable 155 | 156 | expected = checker.ParamTypeErrorMsg("DoSomeIOStuff", 157 | "f", 158 | union.Readable, 159 | pytd.IntersectionType( 160 | [union.Readable, 161 | union.Writable])) 162 | 163 | [actual] = context.exception.args[0] 164 | self.assertEquals(expected, actual) 165 | 166 | # TODO(raoulDoc): more tests! mixing overloading etc 167 | 168 | 169 | if __name__ == "__main__": 170 | unittest.main() 171 | -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | from examples import email 19 | 20 | e = email.Emailer() 21 | email.Emailer.MakeAnnouncement("nobody@example.com") 22 | e.Bonjour() 23 | -------------------------------------------------------------------------------- /examples/StringIO.py: -------------------------------------------------------------------------------- 1 | r"""File-like objects that read from or write to a string buffer. 2 | 3 | WARNING: This version is modified for use as a testcase for python type 4 | checking. Do not use this for anything else!! 5 | 6 | This implements (nearly) all stdio methods. 7 | 8 | f = StringIO() # ready for writing 9 | f = StringIO(buf) # ready for reading 10 | f.close() # explicitly release resources held 11 | flag = f.isatty() # always false 12 | pos = f.tell() # get current position 13 | f.seek(pos) # set current position 14 | f.seek(pos, mode) # mode 0: absolute; 1: relative; 2: relative to EOF 15 | buf = f.read() # read until EOF 16 | buf = f.read(n) # read up to n bytes 17 | buf = f.readline() # read until end of line ('\n') or EOF 18 | list = f.readlines()# list of f.readline() results until EOF 19 | f.truncate([size]) # truncate file at to at most size (default: current pos) 20 | f.write(buf) # write at current position 21 | f.writelines(list) # for line in list: f.write(line) 22 | f.getvalue() # return whole file's contents as a string 23 | 24 | Notes: 25 | - Using a real file is often faster (but less convenient). 26 | - There's also a much faster implementation in C, called cStringIO, but 27 | it's not subclassable. 28 | - fileno() is left unimplemented so that code which uses it triggers 29 | an exception early. 30 | - Seeking far beyond EOF and then writing will insert real null 31 | bytes that occupy space in the buffer. 32 | - There's a simple test set (see end of this file). 33 | """ 34 | try: 35 | from errno import EINVAL 36 | except ImportError: 37 | EINVAL = 22 38 | 39 | __all__ = ["StringIO"] 40 | 41 | def _complain_ifclosed(closed): 42 | if closed: 43 | raise ValueError, "I/O operation on closed file" 44 | 45 | class StringIO: 46 | """class StringIO([buffer]) 47 | 48 | When a StringIO object is created, it can be initialized to an existing 49 | string by passing the string to the constructor. If no string is given, 50 | the StringIO will start empty. 51 | 52 | The StringIO object can accept either Unicode or 8-bit strings, but 53 | mixing the two may take some care. If both are used, 8-bit strings that 54 | cannot be interpreted as 7-bit ASCII (that use the 8th bit) will cause 55 | a UnicodeError to be raised when getvalue() is called. 56 | """ 57 | def __init__(self, buf = ''): 58 | # Force self.buf to be a string or unicode 59 | if not isinstance(buf, basestring): 60 | buf = str(buf) 61 | self.buf = buf 62 | self.len = len(buf) 63 | self.buflist = [] 64 | self.pos = 0 65 | self.closed = False 66 | self.softspace = 0 67 | 68 | def __iter__(self): 69 | return self 70 | 71 | def next(self): 72 | """A file object is its own iterator, for example iter(f) returns f 73 | (unless f is closed). When a file is used as an iterator, typically 74 | in a for loop (for example, for line in f: print line), the next() 75 | method is called repeatedly. This method returns the next input line, 76 | or raises StopIteration when EOF is hit. 77 | """ 78 | _complain_ifclosed(self.closed) 79 | r = self.readline() 80 | if not r: 81 | raise StopIteration 82 | return r 83 | 84 | def close(self): 85 | """Free the memory buffer. 86 | """ 87 | if not self.closed: 88 | self.closed = True 89 | del self.buf, self.pos 90 | 91 | def isatty(self): 92 | """Returns False because StringIO objects are not connected to a 93 | tty-like device. 94 | """ 95 | _complain_ifclosed(self.closed) 96 | return False 97 | 98 | def seek(self, pos, mode = 0): 99 | """Set the file's current position. 100 | 101 | The mode argument is optional and defaults to 0 (absolute file 102 | positioning); other values are 1 (seek relative to the current 103 | position) and 2 (seek relative to the file's end). 104 | 105 | There is no return value. 106 | """ 107 | _complain_ifclosed(self.closed) 108 | if self.buflist: 109 | self.buf += ''.join(self.buflist) 110 | self.buflist = [] 111 | if mode == 1: 112 | pos += self.pos 113 | elif mode == 2: 114 | pos += self.len 115 | self.pos = max(0, pos) 116 | 117 | def tell(self): 118 | """Return the file's current position.""" 119 | _complain_ifclosed(self.closed) 120 | return self.pos 121 | 122 | def read(self, n = -1): 123 | """Read at most size bytes from the file 124 | (less if the read hits EOF before obtaining size bytes). 125 | 126 | If the size argument is negative or omitted, read all data until EOF 127 | is reached. The bytes are returned as a string object. An empty 128 | string is returned when EOF is encountered immediately. 129 | """ 130 | _complain_ifclosed(self.closed) 131 | if self.buflist: 132 | self.buf += ''.join(self.buflist) 133 | self.buflist = [] 134 | if n is None or n < 0: 135 | newpos = self.len 136 | else: 137 | newpos = min(self.pos+n, self.len) 138 | r = self.buf[self.pos:newpos] 139 | self.pos = newpos 140 | return r 141 | 142 | def readline(self, length=None): 143 | r"""Read one entire line from the file. 144 | 145 | A trailing newline character is kept in the string (but may be absent 146 | when a file ends with an incomplete line). If the size argument is 147 | present and non-negative, it is a maximum byte count (including the 148 | trailing newline) and an incomplete line may be returned. 149 | 150 | An empty string is returned only when EOF is encountered immediately. 151 | 152 | Note: Unlike stdio's fgets(), the returned string contains null 153 | characters ('\0') if they occurred in the input. 154 | """ 155 | _complain_ifclosed(self.closed) 156 | if self.buflist: 157 | self.buf += ''.join(self.buflist) 158 | self.buflist = [] 159 | i = self.buf.find('\n', self.pos) 160 | if i < 0: 161 | newpos = self.len 162 | else: 163 | newpos = i+1 164 | if length is not None and length >= 0: 165 | if self.pos + length < newpos: 166 | newpos = self.pos + length 167 | r = self.buf[self.pos:newpos] 168 | self.pos = newpos 169 | return r 170 | 171 | def readlines(self, sizehint = 0): 172 | """Read until EOF using readline() and return a list containing the 173 | lines thus read. 174 | 175 | If the optional sizehint argument is present, instead of reading up 176 | to EOF, whole lines totalling approximately sizehint bytes (or more 177 | to accommodate a final whole line). 178 | """ 179 | total = 0 180 | lines = [] 181 | line = self.readline() 182 | while line: 183 | lines.append(line) 184 | total += len(line) 185 | if 0 < sizehint <= total: 186 | break 187 | line = self.readline() 188 | return lines 189 | 190 | def truncate(self, size=None): 191 | """Truncate the file's size. 192 | 193 | If the optional size argument is present, the file is truncated to 194 | (at most) that size. The size defaults to the current position. 195 | The current file position is not changed unless the position 196 | is beyond the new file size. 197 | 198 | If the specified size exceeds the file's current size, the 199 | file remains unchanged. 200 | """ 201 | _complain_ifclosed(self.closed) 202 | if size is None: 203 | size = self.pos 204 | elif size < 0: 205 | raise IOError(EINVAL, "Negative size not allowed") 206 | elif size < self.pos: 207 | self.pos = size 208 | self.buf = self.getvalue()[:size] 209 | self.len = size 210 | 211 | def write(self, s): 212 | """Write a string to the file. 213 | 214 | There is no return value. 215 | """ 216 | _complain_ifclosed(self.closed) 217 | if not s: return 218 | # Force s to be a string or unicode 219 | if not isinstance(s, basestring): 220 | s = str(s) 221 | spos = self.pos 222 | slen = self.len 223 | if spos == slen: 224 | self.buflist.append(s) 225 | self.len = self.pos = spos + len(s) 226 | return 227 | if spos > slen: 228 | self.buflist.append('\0'*(spos - slen)) 229 | slen = spos 230 | newpos = spos + len(s) 231 | if spos < slen: 232 | if self.buflist: 233 | self.buf += ''.join(self.buflist) 234 | self.buflist = [self.buf[:spos], s, self.buf[newpos:]] 235 | self.buf = '' 236 | if newpos > slen: 237 | slen = newpos 238 | else: 239 | self.buflist.append(s) 240 | slen = newpos 241 | self.len = slen 242 | self.pos = newpos 243 | 244 | def writelines(self, iterable): 245 | """Write a sequence of strings to the file. The sequence can be any 246 | iterable object producing strings, typically a list of strings. There 247 | is no return value. 248 | 249 | (The name is intended to match readlines(); writelines() does not add 250 | line separators.) 251 | """ 252 | write = self.write 253 | for line in iterable: 254 | write(line) 255 | 256 | def flush(self): 257 | """Flush the internal buffer 258 | """ 259 | _complain_ifclosed(self.closed) 260 | 261 | def getvalue(self): 262 | """ 263 | Retrieve the entire contents of the "file" at any time before 264 | the StringIO object's close() method is called. 265 | 266 | The StringIO object can accept either Unicode or 8-bit strings, 267 | but mixing the two may take some care. If both are used, 8-bit 268 | strings that cannot be interpreted as 7-bit ASCII (that use the 269 | 8th bit) will cause a UnicodeError to be raised when getvalue() 270 | is called. 271 | """ 272 | _complain_ifclosed(self.closed) 273 | if self.buflist: 274 | self.buf += ''.join(self.buflist) 275 | self.buflist = [] 276 | return self.buf 277 | 278 | 279 | # A little test suite 280 | 281 | def test(): 282 | import sys 283 | if sys.argv[1:]: 284 | file = sys.argv[1] 285 | else: 286 | file = '/etc/passwd' 287 | lines = open(file, 'r').readlines() 288 | text = open(file, 'r').read() 289 | f = StringIO() 290 | for line in lines[:-2]: 291 | f.write(line) 292 | f.writelines(lines[-2:]) 293 | if f.getvalue() != text: 294 | raise RuntimeError, 'write failed' 295 | length = f.tell() 296 | print 'File length =', length 297 | f.seek(len(lines[0])) 298 | f.write(lines[1]) 299 | f.seek(0) 300 | print 'First line =', repr(f.readline()) 301 | print 'Position =', f.tell() 302 | line = f.readline() 303 | print 'Second line =', repr(line) 304 | f.seek(-len(line), 1) 305 | line2 = f.read(len(line)) 306 | if line != line2: 307 | raise RuntimeError, 'bad result after seek back' 308 | f.seek(len(line2), 1) 309 | list = f.readlines() 310 | line = list[-1] 311 | f.seek(f.tell() - len(line)) 312 | line2 = f.read() 313 | if line != line2: 314 | raise RuntimeError, 'bad result after seek back from EOF' 315 | print 'Read', len(list), 'more lines' 316 | print 'File length =', f.tell() 317 | if f.tell() != length: 318 | raise RuntimeError, 'bad length' 319 | f.truncate(length/2) 320 | f.seek(0, 2) 321 | print 'Truncated length =', f.tell() 322 | if f.tell() != length/2: 323 | raise RuntimeError, 'truncate did not adjust length' 324 | f.close() 325 | 326 | if __name__ == '__main__': 327 | test() 328 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/pytypedecl/c7a1bdd9044b00b67e1fc21a48365e424eace0c0/examples/__init__.py -------------------------------------------------------------------------------- /examples/email.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | """A demo which uses types to find an error.""" 19 | 20 | import sys 21 | from pytypedecl import checker 22 | 23 | 24 | class Emailer(object): 25 | """An dummy emailer class. 26 | """ 27 | 28 | @classmethod 29 | def MakeAnnouncement(cls, emails): 30 | for addr in emails: 31 | cls.SendEmail(addr) 32 | 33 | @classmethod 34 | def SendEmail(cls, addr): 35 | print "sending email to " + addr 36 | 37 | def Hello(self): 38 | print "hello" 39 | 40 | def Bonjour(self): 41 | print "bonjour" 42 | 43 | 44 | checker.CheckFromFile(sys.modules[__name__], __file__ + "td") 45 | -------------------------------------------------------------------------------- /examples/email.pytd: -------------------------------------------------------------------------------- 1 | # -*- mode: python; coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | class Emailer: 19 | def MakeAnnouncement(cls, emails:list) -> None 20 | def SendEmail(cls, addr:str) -> None 21 | def Bonjour(self) -> str 22 | -------------------------------------------------------------------------------- /parse/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/pytypedecl/c7a1bdd9044b00b67e1fc21a48365e424eace0c0/parse/__init__.py -------------------------------------------------------------------------------- /parse/ast.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # TODO: Combine with the 'typing' module? 18 | 19 | 20 | """Classes used by parser to generate an AST. 21 | """ 22 | 23 | import collections 24 | from pytypedecl.parse import typed_tuple 25 | 26 | 27 | class TypeDeclUnit(typed_tuple.Eq, collections.namedtuple( 28 | 'TypeDeclUnit', ['interfacedefs', 'classdefs', 'funcdefs'])): 29 | """Top level node. Holds a list of Function nodes. 30 | 31 | Attributes: 32 | funcdefs: A list of functions defined in this type decl unit. 33 | interfacedefs: A list of interfaces defined in this type decl unit. 34 | classdefs: A list of interfaces defined in this type decl unit. 35 | """ 36 | __slots__ = () 37 | 38 | def ExpandTemplates(self, rev_templates): 39 | return self._replace( 40 | interfacedefs=[i.ExpandTemplates(rev_templates) 41 | for i in self.interfacedefs], 42 | classdefs=[c.ExpandTemplates(rev_templates) for c in self.classdefs], 43 | funcdefs=[f.ExpandTemplates(rev_templates) for f in self.funcdefs]) 44 | 45 | 46 | class Interface(typed_tuple.Eq, collections.namedtuple( 47 | 'Interface', ['name', 'parents', 'attrs', 'template'])): 48 | __slots__ = () 49 | 50 | def ExpandTemplates(self, rev_templates): 51 | rev_t = [self.template] + rev_templates 52 | return self._replace(attrs=[a.ExpandTemplates(rev_t) for a in self.attrs]) 53 | 54 | 55 | class Class(typed_tuple.Eq, collections.namedtuple( 56 | 'Class', ['name', 'parents', 'funcs', 'template'])): 57 | __slots__ = () 58 | 59 | def ExpandTemplates(self, rev_templates): 60 | rev_t = [self.template] + rev_templates 61 | return self._replace(funcs=[f.ExpandTemplates(rev_t) for f in self.funcs]) 62 | 63 | 64 | class Function(typed_tuple.Eq, collections.namedtuple( 65 | 'Function', ['name', 'params', 'return_type', 'exceptions', 'template', 66 | 'provenance', 'signature'])): 67 | """Represents a function definition. 68 | 69 | Attributes: 70 | name: The name of this function. 71 | params: The list of parameters for this function definition. 72 | return_type: The return type of this function. 73 | exceptions: A list of exceptions for this function definition. 74 | template: names for bindings for bounded types in params/return_type 75 | provenance: TBD 76 | signature: TBD 77 | 78 | # TODO: define/implement provenance: 79 | ... inferred 80 | --- programmer-deleted 81 | +++ locked (no need to look inside it ... all declarations 82 | for this function must be marked with +++ or --- 83 | (nothing) programmer-approved 84 | # TODO: implement signature 85 | """ 86 | __slots__ = () 87 | 88 | def ExpandTemplates(self, rev_templates): 89 | rev_t = [self.template] + rev_templates 90 | return self._replace( 91 | params=[p.ExpandTemplates(rev_t) for p in self.params], 92 | return_type=self.return_type.ExpandTemplates(rev_t), 93 | exceptions=[e.ExpandTemplates(rev_t) for e in self.exceptions]) 94 | 95 | 96 | class ConstantDef(typed_tuple.Eq, collections.namedtuple( 97 | 'ConstantDef', ['name', 'type'])): 98 | __slots__ = () 99 | 100 | 101 | class MinimalFunction(typed_tuple.Eq, collections.namedtuple( 102 | 'MinimalFunction', ['name'])): 103 | """Like Function, but without params etc.""" 104 | __slots__ = () 105 | 106 | def ExpandTemplates(self, unused_rev_templates): 107 | return self 108 | 109 | 110 | class ExceptionDef(typed_tuple.Eq, collections.namedtuple( 111 | 'ExceptionDef', ['containing_type'])): 112 | """Represents an exception. 113 | 114 | Attributes: 115 | name: The exception typ. 116 | """ 117 | __slots__ = () 118 | 119 | def ExpandTemplates(self, rev_templates): 120 | return self._replace( 121 | containing_type=self.containing_type.ExpandTemplates(rev_templates)) 122 | 123 | 124 | class Parameter(typed_tuple.Eq, collections.namedtuple( 125 | 'Parameter', ['name', 'type'])): 126 | """Represents a parameter of a function definition. 127 | 128 | Attributes: 129 | name: The name of the parameter. 130 | type: The type of the parameter. 131 | """ 132 | __slots__ = () 133 | 134 | def ExpandTemplates(self, rev_templates): 135 | return self._replace(type=self.type.ExpandTemplates(rev_templates)) 136 | 137 | 138 | class TemplateItem(typed_tuple.Eq, collections.namedtuple( 139 | 'TemplateItem', ['name', 'within_type', 'level'])): 140 | """Represents "template name <= bounded_type". 141 | 142 | This can be either the result of the 'template' in the parser (e.g., 143 | funcdef : provenance DEF template NAME LPAREN params RPAREN ...) 144 | or the result of a lookup using the ExpandTemplates method. 145 | 146 | Attributes: 147 | name: the name that's used in a generic type 148 | type: the "<=" type for this name (e.g., BasicType('object')) 149 | level: When this object is the result of a lookup, it is how many 150 | levels "up" the name was found. For example: 151 | class [T] Foo: 152 | def [U] bar(t: T, u: U) 153 | in the definition of 'bar', T has level=1 and U has level=0 154 | """ 155 | __slots__ = () 156 | 157 | def ExpandTemplates(self, rev_templates): 158 | return self._replace(self.within_type.ExpandTemplates(rev_templates)) 159 | 160 | def Process(self, processor): 161 | return processor.ProcessTemplateItem(self) 162 | -------------------------------------------------------------------------------- /parse/builtins.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | """Utilities for parsing pytd files for builtins.""" 19 | 20 | import os.path 21 | 22 | from pytypedecl import utils 23 | from pytypedecl.parse import parser 24 | from pytypedecl.parse import visitors 25 | 26 | 27 | def _FindBuiltinFile(name): 28 | return utils.GetDataFile(os.path.join("builtins", name)) 29 | 30 | 31 | # TODO: Use a memoizing decorator instead. 32 | # Keyed by the parameter(s) passed to GetBuiltins: 33 | _cached_builtins = {} 34 | 35 | 36 | def GetBuiltins(stdlib=True, builtin_name="__builtin__"): 37 | """Get the "default" AST used to lookup built in types. 38 | 39 | Get an AST for all Python builtins as well as the most commonly used standard 40 | libraries. 41 | 42 | Args: 43 | stdlib: Whether to load the standard library, too. If this is False, 44 | TypeDeclUnit.modules will be empty. If it's True, it'll contain modules 45 | like itertools and signal. 46 | builtin_name: The base part of the builtins file name. 47 | 48 | Returns: 49 | A pytd.TypeDeclUnit instance. It'll directly contain the builtin classes 50 | and functions, and submodules for each of the standard library modules. 51 | """ 52 | cache_key = stdlib 53 | if cache_key in _cached_builtins: 54 | return _cached_builtins[cache_key] 55 | # TODO: This can be fairly slow; suggest pickling the result and 56 | # reusing if possible (see lib2to3.pgen2.grammar) 57 | 58 | # We use the same parser instance to parse all builtin files. This changes 59 | # the run time from 1.0423s to 0.5938s (for 21 builtins). 60 | p = parser.TypeDeclParser() 61 | builtins = p.Parse( 62 | _FindBuiltinFile(builtin_name + ".pytd"), name=builtin_name) 63 | # We list modules explicitly, because we might have to extract them out of 64 | # a PAR file, which doesn't have good support for listing directories. 65 | modules = ["array", "codecs", "errno", "fcntl", "gc", "itertools", "marshal", 66 | "os", "posix", "pwd", "select", "signal", "_sre", "StringIO", 67 | "strop", "_struct", "sys", "_warnings", "warnings", "_weakref"] 68 | if stdlib: 69 | builtins = builtins.Replace( 70 | modules=tuple(p.Parse(_FindBuiltinFile(mod + ".pytd"), 71 | filename=mod + ".pytd", name=mod) 72 | for mod in modules)) 73 | _cached_builtins[cache_key] = builtins 74 | return builtins 75 | 76 | 77 | def GetBuiltinsHierarchy(): 78 | builtins = GetBuiltins() 79 | return builtins.Visit(visitors.ExtractSuperClassesByName()) 80 | -------------------------------------------------------------------------------- /parse/builtins_test.py: -------------------------------------------------------------------------------- 1 | """Tests for parse.builtins.""" 2 | 3 | import unittest 4 | 5 | 6 | from pytypedecl import pytd 7 | from pytypedecl.parse import builtins 8 | from pytypedecl.parse import visitors 9 | 10 | 11 | class UtilsTest(unittest.TestCase): 12 | 13 | @classmethod 14 | def setUpClass(cls): 15 | cls.builtins = builtins.GetBuiltins() 16 | 17 | def testGetBuiltins(self): 18 | self.assertIsNotNone(self.builtins) 19 | self.assertTrue(hasattr(self.builtins, "modules")) 20 | # Will throw an error for unresolved identifiers: 21 | visitors.LookupClasses(self.builtins) 22 | 23 | def testHasMutableParameters(self): 24 | append = self.builtins.Lookup("list").Lookup("append") 25 | self.assertIsInstance(append.signatures[0].params[0], pytd.MutableParameter) 26 | 27 | def testHasCorrectSelf(self): 28 | update = self.builtins.Lookup("dict").Lookup("update") 29 | t = update.signatures[0].params[0].type 30 | self.assertIsInstance(t, pytd.GenericType) 31 | self.assertEquals(t.base_type, pytd.NamedType("dict")) 32 | 33 | def testHasObjectSuperClass(self): 34 | cls = self.builtins.Lookup("int") 35 | self.assertEquals(cls.parents, (pytd.NamedType("object"),)) 36 | cls = self.builtins.Lookup("object") 37 | self.assertEquals(cls.parents, ()) 38 | 39 | 40 | if __name__ == "__main__": 41 | unittest.main() 42 | -------------------------------------------------------------------------------- /parse/decorate.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2014 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # TODO: This is experimental. 18 | 19 | """Allows "decorating" the classes of a nested tree. 20 | 21 | Allows changing classes in a tree to ones with more functionality, by returning 22 | a new tree with nodes that were reconstructed using the newer class. 23 | 24 | Usage: 25 | 26 | # "decorate" is, essentially, a map that collects all classes it decorates. 27 | # Needed since we don't want different decorators conflicting. 28 | decorate = Decorator() 29 | 30 | @decorate 31 | class ClassType(pytd.ClassType): 32 | def Print(self): 33 | ... 34 | 35 | @decorate 36 | class NamedType(pytd.NamedType): 37 | def Print(self): 38 | ... 39 | 40 | node = decorate.Visit(node) 41 | """ 42 | 43 | 44 | class Decorator(object): 45 | """A class decorator to collect node replacements.""" 46 | 47 | def __init__(self): 48 | self._mapping = {} 49 | 50 | def __call__(self, cls): 51 | """'Decorate' a given class. Only stores it for later.""" 52 | self._mapping[cls.__name__] = cls 53 | return cls 54 | 55 | def Visit(self, node): 56 | """Replace a tree of nodes with nodes registered as replacements. 57 | 58 | This will walk the tree and replace each class with a class of the same 59 | name previously registered by using this class as a class decorator. 60 | 61 | Args: 62 | node: A pytd node. 63 | 64 | Returns: 65 | A new tree, with given nodes taken over by their replacement classes. 66 | """ 67 | mapping = self._mapping 68 | 69 | # Build a visitor that performs the old_class -> new_class mapping: 70 | class Visitor(object): 71 | name_to_class = mapping 72 | for name, new_cls in mapping.iteritems(): 73 | 74 | def Visit(self, node): 75 | # Python doesn't allow us to build this as a closure, so we have to 76 | # use the clunky way of retrieving the replacement class. 77 | cls = self.name_to_class[node.__class__.__name__] 78 | return cls(*node) 79 | locals()["Visit" + name] = Visit 80 | return node.Visit(Visitor()) 81 | -------------------------------------------------------------------------------- /parse/node.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2014 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | """Extension of collections.namedtuple for use in representing immutable trees. 19 | 20 | Example usage: 21 | 22 | class Data(Node("d1", "d2", "d3")): 23 | pass 24 | class X(Node("a", "b")): 25 | pass 26 | class Y(Node("c", "d")): 27 | pass 28 | class XY(Node("x", "y")): 29 | pass 30 | data = Data(42, 43, 44) 31 | x = X(1, [1, 2]) 32 | y = Y([1], {"data": data}) 33 | xy = XY(x, y) 34 | 35 | class Visitor(object): 36 | def X(self): 37 | count_x += 1 38 | def VisitData(self, node): 39 | return node.Replace(d3=1000) 40 | 41 | new_xy = xy.Visit(Visitor()) 42 | 43 | The Node "class" differs from namedtuple in the following ways: 44 | 45 | 1.) More stringent equality test. collections.namedtuple.__eq__ is implicitly 46 | tuple equality (which makes two tuples equal if all their values are 47 | recursively equal), but that allows two objects to be the same if they 48 | happen to have the same field values. 49 | To avoid this problem, Node adds the check that the two objects' classes are 50 | equal (this might be too strong, in which case you'd need to use isinstance 51 | checks). 52 | 2.) Visitor interface. See documentation of Visit() below. 53 | 3.) Subclassed __str__ function that uses the current class name instead of 54 | the name of the tuple this class is based on. 55 | 56 | See http://bugs.python.org/issue16279 for why it is unlikely for any these 57 | functionalities to be made part of collections.namedtuple. 58 | """ 59 | 60 | import collections 61 | import itertools 62 | 63 | 64 | def Node(*child_names): 65 | """Create a new Node class. 66 | 67 | You will typically use this when declaring a new class. 68 | For example: 69 | class Coordinate(Node("x", "y")): 70 | pass 71 | 72 | Arguments: 73 | *child_names: Names of the children of this node. 74 | 75 | Returns: 76 | A subclass of (named)tuple. 77 | """ 78 | 79 | namedtuple_type = collections.namedtuple("_", child_names) 80 | 81 | class NamedTupleNode(namedtuple_type): 82 | """A Node class based on namedtuple.""" 83 | 84 | def __eq__(self, other): 85 | """Compare two nodes for equality. 86 | 87 | This will return True if the two underlying tuples are the same *and* the 88 | two node types match. 89 | 90 | Arguments: 91 | other: The Node to compare this one with. 92 | Returns: 93 | True or False. 94 | """ 95 | # This comparison blows up if "other" is an old-style class (not an 96 | # instance). That's fine, because trying to compare a tuple to a class is 97 | # almost certainly a programming error, and blowing up is better than 98 | # silently returning False. 99 | if self is other: 100 | return True 101 | elif self.__class__ is other.__class__: 102 | return tuple.__eq__(self, other) 103 | else: 104 | return False 105 | 106 | def __ne__(self, other): 107 | """Compare two nodes for inequality. See __eq__.""" 108 | return not self == other 109 | 110 | def __lt__(self, other): 111 | """Smaller than other node? Define so we can to deterministic ordering.""" 112 | if self is other: 113 | return False 114 | elif self.__class__ is other.__class__: 115 | return tuple.__lt__(self, other) 116 | else: 117 | return self.__class__.__name__ < other.__class__.__name__ 118 | 119 | def __gt__(self, other): 120 | """Larger than other node? Define so we can to deterministic ordering.""" 121 | if self is other: 122 | return False 123 | elif self.__class__ is other.__class__: 124 | return tuple.__gt__(self, other) 125 | else: 126 | return self.__class__.__name__ > other.__class__.__name__ 127 | 128 | def __le__(self, other): 129 | return self == other or self < other 130 | 131 | def __ge__(self, other): 132 | return self == other or self > other 133 | 134 | def __repr__(self): 135 | """Returns this tuple converted to a string. 136 | 137 | We output this as (values...). This differs from raw tuple 138 | output in that we use the class name, not the name of the tuple this 139 | class extends. Also, Nodes with only one child will be output as 140 | Name(value), not Name(value,) to match the constructor syntax. 141 | 142 | Returns: 143 | Representation of this tuple as a string, including the class name. 144 | """ 145 | if len(self) == 1: 146 | return "%s(%r)" % (self.__class__.__name__, self[0]) 147 | else: 148 | return "%s%r" % (self.__class__.__name__, tuple(self)) 149 | 150 | # Expose namedtuple._replace as "Replace", so avoid lint warnings 151 | # and have consistent method names. 152 | Replace = namedtuple_type._replace # pylint: disable=no-member,invalid-name 153 | 154 | def Visit(self, visitor, *args, **kwargs): 155 | """Visitor interface for transforming a tree of nodes to a new tree. 156 | 157 | You can pass a visitor, and callback functions on that visitor will be 158 | called for all nodes in the tree. Note that nodes are also allowed to 159 | be stored in lists and as the values of dictionaries, as long as these 160 | lists/dictionaries are stored in the named fields of the Node class. 161 | It's possible to overload the Visit function on Nodes, to do your own 162 | processing. 163 | 164 | Arguments: 165 | visitor: An instance of a visitor for this tree. For every node type you 166 | want to transform, this visitor implements a "Visit" 167 | function named after the class of the node this function should 168 | target. Note that is the *actual* class of the node, so 169 | if you subclass a Node class, visitors for the superclasses will *not* 170 | be triggered anymore. Also, visitor callbacks are only triggered 171 | for subclasses of Node. 172 | *args: Passed to the visitor callback. 173 | **kwargs: Passed to the visitor callback. 174 | 175 | Returns: 176 | Transformed version of this node. 177 | """ 178 | # This function is overwritten below, so that we have the same im_func 179 | # even though we generate classes here. 180 | pass # COV_NF_LINE 181 | Visit = _VisitNode # pylint: disable=invalid-name 182 | 183 | return NamedTupleNode 184 | 185 | 186 | def _VisitNode(node, visitor, *args, **kwargs): 187 | """Transform a node and all its children using a visitor. 188 | 189 | This will iterate over all children of this node, and also process certain 190 | things that are not nodes. The latter are either other supported types of 191 | containers (right now, lists and dictionaries), which will be scanned for 192 | nodes regardless, or primitive types, which will be return as-is. 193 | 194 | Args: 195 | node: The node to transform. Either an actual "instance" of Node, or an 196 | other type of container (lists, dicts) found while scanning a node 197 | tree, or any other type (which will be returned unmodified). 198 | visitor: The visitor to apply. If this visitor has a "Visit" method, 199 | with the name of the Node class, a callback will be triggered, 200 | and the transformed version of this node will be whatever the callback 201 | returned, or the original node if the callback returned None. Before 202 | calling the Visit callback, the following attribute(s) on the Visitor 203 | class will be populated: 204 | vistor.old_node: The node before the child nodes were visited. 205 | 206 | Additionally, if the visitor has a "Enter" method, that method 207 | will be called on the original node before descending into it. If 208 | "Enter" returns False, the visitor will not visit children of 209 | this node (the result of "Enter" is otherwise unused; in 210 | particular it's OK to return None, which will be ignored). 211 | ["Enter" is called pre-order; "Visit and "Leave" are 212 | called post-order.] A counterpart to "Enter" is "Leave", 213 | which is intended for any clean-up that "Enter" needs (other 214 | than that, it's redunddant, and could be combined with "Visit"). 215 | *args: Passed to visitor callbacks. 216 | **kwargs: Passed to visitor callbacks. 217 | Returns: 218 | The transformed Node (which *may* be the original node but could be a new 219 | node, even if the contents are the same). 220 | Raises: 221 | AssertionError: If the visitor specifies to support all node types 222 | (implements_all_node_types), but we find a missing method. 223 | """ 224 | 225 | node_class_name = node.__class__.__name__ 226 | if hasattr(node, "Visit") and node.Visit.im_func != _VisitNode: 227 | # Node with an overloaded Visit() function. It'll do its own processing. 228 | return node.Visit(visitor, *args, **kwargs) 229 | elif isinstance(node, tuple): 230 | enter_function = getattr(visitor, "Enter" + node_class_name, None) 231 | if enter_function: 232 | # The visitor wants to be informed that we're descending into this part 233 | # of the tree. 234 | status = enter_function(node, *args, **kwargs) 235 | # Don't descend if Enter explicitly returns False, but not None, 236 | # since None is the default return of Python functions. 237 | if status is False: 238 | return node 239 | # Any other value returned from Enter is ignored, so check: 240 | assert status is None, repr(node_class_name, status) 241 | 242 | new_children = [_VisitNode(child, visitor, *args, **kwargs) 243 | for child in node] 244 | if any(c1 is not c2 for c1, c2 in itertools.izip(new_children, node)): 245 | # Exact comparison, because classes deriving from tuple (like namedtuple) 246 | # have different constructor arguments. 247 | if node.__class__ is tuple: 248 | new_node = node.__class__(new_children) 249 | else: 250 | # Assume this is a namedtuple. Reinitialize with our current old 251 | # class (because we changed some of the children). The constructor of 252 | # namedtuple() differs from tuple(), so we have to pass the current 253 | # tuple using "*". 254 | new_node = node.__class__(*new_children) 255 | else: 256 | # Optimization: if we didn't change any of the children, keep the entire 257 | # object the same. 258 | # TODO: Does this actually have any benefit? The test is 259 | # moderately expensive and a new node just copies a few 260 | # pointers (and turns over a bit of memory). Nobody 261 | # should depend on the tree remaining identical (object 262 | # identity) if the visitor makes no changes to any node. 263 | new_node = node 264 | 265 | visitor.old_node = node 266 | # Now call the user supplied callback(s), if they exists. Notice we only do 267 | # this for tuples. 268 | visit_function = getattr(visitor, "Visit" + node_class_name, False) 269 | if (getattr(visitor, "implements_all_node_types", False) 270 | and node_class_name != "tuple"): 271 | if not enter_function and not visit_function: 272 | raise AssertionError("Unimplemented visitor: " + node_class_name) 273 | if visit_function: 274 | new_node = visit_function(new_node, *args, **kwargs) 275 | leave_function = getattr(visitor, "Leave" + node_class_name, False) 276 | if leave_function: 277 | # Clean-up from Enter/Visit 278 | leave_function(node, *args, **kwargs) 279 | 280 | del visitor.old_node 281 | return new_node 282 | elif isinstance(node, list): 283 | new_list_entries = [_VisitNode(child, visitor, *args, **kwargs) 284 | for child in node] 285 | if any(c1 is not c2 for c1, c2 in zip(new_list_entries, node)): 286 | # Since some of our children changed, instantiate a new list. 287 | return node.__class__(new_list_entries) 288 | elif isinstance(node, dict): 289 | new_dict = {k: _VisitNode(child, visitor, *args, **kwargs) 290 | for k, child in node.items()} 291 | if any(id(new_dict[k]) != id(node[k]) for k in node): 292 | # Return a new dictionary, but with the current class, in case the user 293 | # subclasses dict. 294 | return node.__class__(new_dict) 295 | return node 296 | -------------------------------------------------------------------------------- /parse/node_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | import itertools 17 | import unittest 18 | from pytypedecl.parse import node 19 | 20 | 21 | # gpylint doesn't understand collections.namedtuple(): 22 | # pylint: disable=no-member 23 | 24 | 25 | class Node1(node.Node("a", "b")): 26 | """Simple node for equality testing. Not equal to anything else.""" 27 | pass 28 | 29 | 30 | class Node2(node.Node("x", "y")): 31 | """For equality testing. Same attributes as Node3.""" 32 | pass 33 | 34 | 35 | class Node3(node.Node("x", "y")): 36 | """For equality testing: Same attributes as Node2.""" 37 | pass 38 | 39 | 40 | class Data(node.Node("d1", "d2", "d3")): 41 | """'Data' node. Visitor tests use this to store numbers in leafs.""" 42 | pass 43 | 44 | 45 | class V(node.Node("x")): 46 | """Inner node 'V', with one child. See testVisitor[...]() below.""" 47 | pass 48 | 49 | 50 | class X(node.Node("a", "b")): 51 | """Inner node 'X', with two children. See testVisitor[...]() below.""" 52 | pass 53 | 54 | 55 | class Y(node.Node("c", "d")): 56 | """Inner node 'Y', with two children. See testVisitor[...]() below.""" 57 | pass 58 | 59 | 60 | class XY(node.Node("x", "y")): 61 | """Inner node 'XY', with two children. See testVisitor[...]() below.""" 62 | pass 63 | 64 | 65 | class NodeWithVisit(node.Node("x", "y")): 66 | """A node with its own Visit function.""" 67 | 68 | def Visit(self, visitor): 69 | """Allow a visitor to modify our children. Returns modified node.""" 70 | # only visit x, not y 71 | x = self.x.Visit(visitor) 72 | return NodeWithVisit(x, self.y) 73 | 74 | 75 | class DataVisitor(object): 76 | """A visitor that transforms Data nodes.""" 77 | 78 | def VisitData(self, data): 79 | """Visit Data nodes, and set 'd3' attribute to -1.""" 80 | return data.Replace(d3=-1) 81 | 82 | 83 | class MultiNodeVisitor(object): 84 | """A visitor that visits Data, V and Y nodes and uses the *args feature.""" 85 | 86 | def VisitData(self, _, r): 87 | """Visit Data nodes, change them to XY nodes, and set x and y.""" 88 | return XY(r, r) 89 | 90 | def VisitV(self, _, r): 91 | """Visit V nodes, change them to X nodes with V nodes as children.""" 92 | return X(V(r), V(r)) 93 | 94 | def VisitY(self, y): 95 | """Visit Y nodes, and change them to X nodes with the same attributes.""" 96 | return X(*y) 97 | 98 | 99 | class TestNode(unittest.TestCase): 100 | """Test the node.Node class generator.""" 101 | 102 | def testEq1(self): 103 | """Test the __eq__ and __ne__ functions of node.Node.""" 104 | n1 = Node1(a=1, b=2) 105 | n2 = Node1(a=1, b=2) 106 | self.assertTrue(n1 == n2) 107 | self.assertFalse(n1 != n2) 108 | 109 | def testEq2(self): 110 | """Test the __eq__ and __ne__ functions of identical nested nodes.""" 111 | n1 = Node1(a=1, b=2) 112 | n2 = Node1(a=1, b=2) 113 | d1 = Node2(x="foo", y=n1) 114 | d2 = Node2(x="foo", y=n1) 115 | d3 = Node2(x="foo", y=n2) 116 | d4 = Node2(x="foo", y=n2) 117 | self.assertTrue(d1 == d2 and d2 == d3 and d3 == d4 and d4 == d1) 118 | # Since node overloads __ne___, too, test it explicitly: 119 | self.assertFalse(d1 != d2 or d2 != d3 or d3 != d4 or d4 != d1) 120 | 121 | def testDeepEq2(self): 122 | """Test the __eq__ and __ne__ functions of differing nested nodes.""" 123 | n1 = Node1(a=1, b=2) 124 | n2 = Node1(a=1, b=3) 125 | d1 = Node2(x="foo", y=n1) 126 | d2 = Node3(x="foo", y=n1) 127 | d3 = Node2(x="foo", y=n2) 128 | d4 = Node3(x="foo", y=n2) 129 | self.assertTrue(d1 != d2) 130 | self.assertTrue(d1 != d3) 131 | self.assertTrue(d1 != d4) 132 | self.assertTrue(d2 != d3) 133 | self.assertTrue(d2 != d4) 134 | self.assertTrue(d3 != d4) 135 | self.assertFalse(d1 == d2) 136 | self.assertFalse(d1 == d3) 137 | self.assertFalse(d1 == d4) 138 | self.assertFalse(d2 == d3) 139 | self.assertFalse(d2 == d4) 140 | self.assertFalse(d3 == d4) 141 | 142 | def testImmutable(self): 143 | """Test that node.Node has/preserves immutatibility.""" 144 | n1 = Node1(a=1, b=2) 145 | n2 = Node2(x="foo", y=n1) 146 | with self.assertRaises(AttributeError): 147 | n1.a = 2 148 | with self.assertRaises(AttributeError): 149 | n2.x = "bar" 150 | with self.assertRaises(AttributeError): 151 | n2.x.b = 3 152 | 153 | def testVisitor1(self): 154 | """Test node.Node.Visit() for a visitor that modifies leaf nodes.""" 155 | data = Data(42, 43, 44) 156 | x = X(1, [1, 2]) 157 | y = Y([V(1)], {"bla": data}) 158 | xy = XY(x, y) 159 | xy_expected = "XY(X(1, [1, 2]), Y([V(1)], {'bla': Data(42, 43, 44)}))" 160 | self.assertEquals(repr(xy), xy_expected) 161 | v = DataVisitor() 162 | new_xy = xy.Visit(v) 163 | self.assertEquals(repr(new_xy), 164 | "XY(X(1, [1, 2]), Y([V(1)], {'bla': Data(42, 43, -1)}))") 165 | self.assertEquals(repr(xy), xy_expected) # check that xy is unchanged 166 | 167 | def testVisitor2(self): 168 | """Test node.Node.Visit() for visitors that modify inner nodes.""" 169 | xy = XY(V(1), Data(1, 2, 3)) 170 | xy_expected = "XY(V(1), Data(1, 2, 3))" 171 | self.assertEquals(repr(xy), xy_expected) 172 | v = MultiNodeVisitor() 173 | new_xy = xy.Visit(v, 42) 174 | self.assertEquals(repr(new_xy), "XY(X(V(42), V(42)), XY(42, 42))") 175 | self.assertEquals(repr(xy), xy_expected) # check that xy is unchanged 176 | 177 | def testRecursion(self): 178 | """Test node.Node.Visit() for visitors that preserve attributes.""" 179 | y = Y(Y(1, 2), Y(3, Y(4, 5))) 180 | y_expected = "Y(Y(1, 2), Y(3, Y(4, 5)))" 181 | self.assertEquals(repr(y), y_expected) 182 | v = MultiNodeVisitor() 183 | new_y = y.Visit(v) 184 | self.assertEquals(repr(new_y), y_expected.replace("Y", "X")) 185 | self.assertEquals(repr(y), y_expected) # check that original is unchanged 186 | 187 | def testTuple(self): 188 | """Test node.Node.Visit() for nodes that contain tuples.""" 189 | v = V((Data(1, 2, 3), Data(4, 5, 6))) 190 | v_expected = "V((Data(1, 2, 3), Data(4, 5, 6)))" 191 | self.assertEquals(repr(v), v_expected) 192 | visit = DataVisitor() 193 | new_v = v.Visit(visit) 194 | new_v_expected = "V((Data(1, 2, -1), Data(4, 5, -1)))" 195 | self.assertEquals(repr(new_v), new_v_expected) 196 | 197 | def testList(self): 198 | """Test node.Node.Visit() for nodes that contain lists.""" 199 | v = V([Data(1, 2, 3), Data(4, 5, 6)]) 200 | v_expected = "V([Data(1, 2, 3), Data(4, 5, 6)])" 201 | self.assertEquals(repr(v), v_expected) 202 | visit = DataVisitor() 203 | new_v = v.Visit(visit) 204 | new_v_expected = "V([Data(1, 2, -1), Data(4, 5, -1)])" 205 | self.assertEquals(repr(new_v), new_v_expected) 206 | 207 | def testEmptyDictionary(self): 208 | """Test node.Node.Visit() for nodes that contain empty dictionaries.""" 209 | visit = DataVisitor() 210 | v = V({}) 211 | new_v = v.Visit(visit) 212 | self.assertEquals(new_v, v) 213 | 214 | def testDictionary(self): 215 | """Test node.Node.Visit() for nodes that contain dictionaries.""" 216 | v = V({1: Data(1, 2, 3), 2: Data(4, 5, 6)}) 217 | new_v = v.Visit(DataVisitor()) 218 | self.assertEquals(new_v.x[1], Data(1, 2, -1)) 219 | self.assertEquals(new_v.x[2], Data(4, 5, -1)) 220 | 221 | def testCustomVisit(self): 222 | """Test nodes that have their own Visit() function.""" 223 | n = Y(NodeWithVisit(Y(1, 2), Y(1, 2)), None) 224 | n_expected = "Y(NodeWithVisit(Y(1, 2), Y(1, 2)), None)" 225 | self.assertEquals(repr(n), n_expected) 226 | visit = MultiNodeVisitor() 227 | new_n = n.Visit(visit) 228 | new_n_expected = "X(NodeWithVisit(X(1, 2), Y(1, 2)), None)" 229 | self.assertEquals(repr(new_n), new_n_expected) 230 | 231 | def testOrdering(self): 232 | nodes = [Node1(1, 1), Node1(1, 2), 233 | Node2(1, 1), Node2(2, 1), 234 | Node3(1, 1), Node3(2, 2), 235 | V(2)] 236 | for n1, n2 in zip(nodes[:-1], nodes[1:]): 237 | self.assertLess(n1, n2) 238 | self.assertLessEqual(n1, n2) 239 | self.assertGreater(n2, n1) 240 | self.assertGreaterEqual(n2, n1) 241 | for p in itertools.permutations(nodes): 242 | self.assertEquals(list(sorted(p)), nodes) 243 | 244 | if __name__ == "__main__": 245 | unittest.main() 246 | -------------------------------------------------------------------------------- /parse/parser_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | # Copyright 2013 Google Inc. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Utility classes for testing the PYTD parser.""" 17 | 18 | import sys 19 | import textwrap 20 | import unittest 21 | from pytypedecl import pytd 22 | from pytypedecl.parse import parser 23 | from pytypedecl.parse import visitors 24 | 25 | 26 | class ParserTest(unittest.TestCase): 27 | """Test utility class. Knows how to parse PYTD and compare source code.""" 28 | 29 | def setUp(self): 30 | self.parser = parser.TypeDeclParser() 31 | 32 | def Parse(self, src, version=None): 33 | # TODO: Using self.parser here breaks tests. Why? 34 | tree = parser.TypeDeclParser(version=version).Parse(textwrap.dedent(src)) 35 | tree.Visit(visitors.VerifyVisitor()) 36 | return tree 37 | 38 | def ToSource(self, src_or_tree): 39 | # TODO: The callers are not consistent in how they use this 40 | # and in most (all?) cases they know whether they're 41 | # passing in a source string or parse tree. It would 42 | # be better if all the calles were consistent. 43 | if isinstance(src_or_tree, basestring): 44 | # If we trust Parse and Print, we can canonical-ize by: 45 | # return pytd.Print(self.Parse(src_or_tree + "\n")) 46 | # This depends on pytd.Print not changing indents, which shouldn't happen: 47 | return src_or_tree 48 | else: # isinstance(src_or_tree, tuple): 49 | src_or_tree.Visit(visitors.VerifyVisitor()) 50 | return pytd.Print(src_or_tree) 51 | 52 | def AssertSourceEquals(self, src_or_tree_1, src_or_tree_2): 53 | # Strip leading "\n"s for convenience 54 | src1 = self.ToSource(src_or_tree_1).strip() + "\n" 55 | src2 = self.ToSource(src_or_tree_2).strip() + "\n" 56 | # Due to differing opinions on the form of debug output, do 57 | # two checks: 58 | if src1 != src2: 59 | sys.stdout.flush() 60 | sys.stderr.flush() 61 | print >>sys.stderr, "Source files differ:" 62 | print >>sys.stderr, "-" * 36, " Actual ", "-" * 36 63 | print >>sys.stderr, textwrap.dedent(src1).strip() 64 | print >>sys.stderr, "-" * 36, "Expected", "-" * 36 65 | print >>sys.stderr, textwrap.dedent(src2).strip() 66 | print >>sys.stderr, "-" * 80 67 | self.maxDiff = None # for better diff output (assertMultiLineEqual) 68 | self.assertMultiLineEqual(src1, src2) 69 | self.fail("source files differ") # Should never reach here 70 | 71 | def ApplyVisitorToString(self, data, visitor): 72 | tree = self.Parse(data) 73 | new_tree = tree.Visit(visitor) 74 | return pytd.Print(new_tree) 75 | -------------------------------------------------------------------------------- /parse/typed_tuple.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | """Add more stringent equality test to collections.namedtuple. 19 | 20 | The Eq class is a mixin for verifying that the two collections.namedtuple items 21 | being tested for equality are of the same class. 22 | 23 | See ast_type.py for how to use. 24 | 25 | See http://bugs.python.org/issue16279 for why this is unlikely to be made part 26 | of collections.namedtuple. 27 | """ 28 | 29 | 30 | class Eq(object): 31 | """Mixin for adding class equality to collections.namedtuple equality. 32 | 33 | It assumes that it is used with a class that was defined by 34 | collections.namedtuple (this is *not* verified). 35 | 36 | collections.namedtuple.__eq__ is implicitly tuple equality (which makes two 37 | tuples equal if all their values are recursively equal), but that allows two 38 | objects to be the same if they happen to have the same field values. To avoid 39 | this problem, you can mixin TupleEq, which adds the check that the two 40 | objects' classes are equal (this might be too strong, in which case you'd need 41 | to use isinstance checks). This mixin must be *first* to ensure it takes 42 | precedence of tuple.__eq__ (or define your own __eq__ using super(); same for 43 | __ne__). 44 | 45 | """ 46 | 47 | def __eq__(self, other): 48 | if self.__class__ is other.__class__: 49 | return tuple.__eq__(self, other) 50 | else: 51 | return NotImplemented 52 | 53 | def __ne__(self, other): 54 | if self.__class__ is other.__class__: 55 | return tuple.__ne__(self, other) 56 | else: 57 | # TODO: This is inconsistent with __eq__ (NoImplemented <-> True) 58 | return True 59 | -------------------------------------------------------------------------------- /parse/typing.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | """Different kinds of types for the parser and typechecker. 19 | 20 | Each type has a Process method that takes a 'processor', which implements the 21 | necessary callbacks. In this way, the processor can keep state, and can be used 22 | to do node-specific processing, such as pretty-printing or creating constraints. 23 | Typically, the caller will walk the tree and call itself via the Process 24 | method. For example: 25 | 26 | class Printer(object): 27 | 28 | def WalkFunc(self, func): 29 | Print(func.name, ', '.join(p.type.Process(self) for p in func.params)) 30 | 31 | # The Process callbacks: 32 | 33 | def ProcessBasicType(self, t): 34 | return t.containing_type 35 | 36 | def ProcessUnionType(self, t): 37 | return 'UNION({})'.format(', '.join( 38 | u.Process(self) for u in t.type_list)) 39 | 40 | ... etc. ... 41 | 42 | The ExpandTemplates method is used to look up names in the AST and replace them 43 | by ast.TemplateItem from the look-up. The 'rev_templates' argument is the list 44 | of templates in reverse order (most recent one first). 45 | 46 | """ 47 | 48 | 49 | import collections 50 | from pytypedecl.parse import typed_tuple 51 | 52 | 53 | class BasicType(typed_tuple.Eq, collections.namedtuple( 54 | 'BasicType', ['containing_type'])): 55 | __slots__ = () 56 | 57 | def ExpandTemplates(self, rev_templates): 58 | for level, templ in enumerate(rev_templates): 59 | for t in templ: 60 | if self.containing_type == t.name: 61 | return t._replace(level=level) # TemplateItem 62 | else: 63 | return self 64 | 65 | def Process(self, processor): 66 | return processor.ProcessBasicType(self) 67 | 68 | 69 | class ConstType(typed_tuple.Eq, collections.namedtuple( 70 | 'ConstType', ['value'])): 71 | __slots__ = () 72 | 73 | def ExpandTemplates(self, unused_rev_templates): 74 | return self 75 | 76 | def Process(self, processor): 77 | return processor.ProcessConstType(self) 78 | 79 | 80 | class NoneAbleType(typed_tuple.Eq, collections.namedtuple( 81 | 'NoneAbleType', ['base_type'])): 82 | __slots__ = () 83 | 84 | def ExpandTemplates(self, unused_rev_templates): 85 | return self 86 | 87 | def Process(self, processor): 88 | return processor.ProcessNonableType(self) 89 | 90 | 91 | class UnionType(typed_tuple.Eq, collections.namedtuple( 92 | 'UnionType', ['type_list'])): 93 | __slots__ = () 94 | 95 | def ExpandTemplates(self, rev_templates): 96 | return self._replace( 97 | type_list=[t.ExpandTemplates(rev_templates) for t in self.type_list]) 98 | 99 | def Process(self, processor): 100 | return processor.ProcessUnionType(self) 101 | 102 | 103 | class IntersectionType(typed_tuple.Eq, collections.namedtuple( 104 | 'IntersectionType', ['type_list'])): 105 | __slots__ = () 106 | 107 | def ExpandTemplates(self, rev_templates): 108 | return self._replace( 109 | type_list=[t.ExpandTemplates(rev_templates) for t in self.type_list]) 110 | 111 | def Process(self, processor): 112 | return processor.ProcessIntersectionType(self) 113 | 114 | 115 | class StructType(typed_tuple.Eq, collections.namedtuple( 116 | 'StructType', ['ops'])): 117 | __slots__ = () 118 | 119 | # There's no ExpandTemplates method because StructType isn't 120 | # created by the parser. 121 | 122 | def Process(self, processor): 123 | return processor.ProcessStructType(self) 124 | 125 | # Extra initialization ... see 126 | # http://stackoverflow.com/questions/3624753/how-to-provide-additional-initialization-for-a-subclass-of-namedtuple 127 | 128 | def __new__(cls, ops): 129 | return super(StructType, cls).__new__(cls, sorted(set(ops))) 130 | 131 | 132 | class GenericType1(typed_tuple.Eq, collections.namedtuple( 133 | 'GenericType1', ['base_type', 'type1'])): 134 | __slots__ = () 135 | 136 | def ExpandTemplates(self, rev_templates): 137 | return self._replace( 138 | base_type=self.base_type.ExpandTemplates(rev_templates), 139 | type1=self.type1.ExpandTemplates(rev_templates)) 140 | 141 | def Process(self, processor): 142 | return processor.ProcessGenericType1(self) 143 | 144 | 145 | class GenericType2(typed_tuple.Eq, collections.namedtuple( 146 | 'GenericType2', ['base_type', 'type1', 'type2'])): 147 | """Constructor for types taking two type arguments. 148 | 149 | Attributes: 150 | base_type: type that is parameterized. E.g. dict 151 | type1: first type parameter. E.g. dict[type1, type2] 152 | type2: second type parameter. E.g. dict[type1, type2] 153 | """ 154 | __slots__ = () 155 | 156 | def ExpandTemplates(self, rev_templates): 157 | return self._replace( 158 | base_type=self.base_type.ExpandTemplates(rev_templates), 159 | type1=self.type1.ExpandTemplates(rev_templates), 160 | type2=self.type2.ExpandTemplates(rev_templates)) 161 | 162 | def Process(self, processor): 163 | return processor.ProcessGenericType2(self) 164 | 165 | 166 | class UnknownType(typed_tuple.Eq, collections.namedtuple('UnknownType', '')): 167 | __slots__ = () 168 | 169 | def ExpandTemplates(self, unused_rev_templatesn): 170 | return self 171 | 172 | def Process(self, processor): 173 | return processor.ProcessUnknownType(self) 174 | 175 | 176 | class OptionalUnknownType(typed_tuple.Eq, 177 | collections.namedtuple('OptionalUnknownType', '')): 178 | __slots__ = () 179 | 180 | def ExpandTemplates(self, unused_rev_templatesn): 181 | return self 182 | 183 | def Process(self, processor): 184 | return processor.ProcessOptionalUnknownType(self) 185 | 186 | class VarArgType(OptionalUnknownType): 187 | "*args" 188 | __slots__ = () 189 | 190 | class VarKeywordArgType(OptionalUnknownType): 191 | "**kwargs" 192 | __slots__ = () 193 | -------------------------------------------------------------------------------- /parse/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | # TODO: Rename this file to builtins.py. 19 | 20 | 21 | """Utilities for parsing type declaration files. 22 | """ 23 | 24 | import os.path 25 | 26 | from pytypedecl import utils 27 | from pytypedecl.parse import parser 28 | from pytypedecl.parse import visitors 29 | 30 | 31 | def _FindBuiltinFile(name): 32 | return utils.GetDataFile(os.path.join("builtins", name)) 33 | 34 | 35 | # TODO: Use a memoizing decorator instead. 36 | # Keyed by the parameter(s) passed to GetBuiltins: 37 | _cached_builtins = {} 38 | 39 | 40 | def GetBuiltins(stdlib=True): 41 | """Get the "default" AST used to lookup built in types. 42 | 43 | Get an AST for all Python builtins as well as the most commonly used standard 44 | libraries. 45 | 46 | Args: 47 | stdlib: Whether to load the standard library, too. If this is False, 48 | TypeDeclUnit.modules will be empty. If it's True, it'll contain modules 49 | like itertools and signal. 50 | 51 | Returns: 52 | A pytd.TypeDeclUnit instance. It'll directly contain the builtin classes 53 | and functions, and submodules for each of the standard library modules. 54 | """ 55 | cache_key = stdlib 56 | if cache_key in _cached_builtins: 57 | return _cached_builtins[cache_key] 58 | # TODO: This can be fairly slow; suggest pickling the result and 59 | # reusing if possible (see lib2to3.pgen2.grammar) 60 | 61 | # We use the same parser instance to parse all builtin files. This changes 62 | # the run time from 1.0423s to 0.5938s (for 21 builtins). 63 | p = parser.TypeDeclParser(parser.DEFAULT_VERSION) 64 | builtins = p.Parse(_FindBuiltinFile("__builtin__.pytd")) 65 | # We list modules explicitly, because we might have to extract them out of 66 | # a PAR file, which doesn't have good support for listing directories. 67 | modules = ["array", "codecs", "errno", "fcntl", "gc", "itertools", "marshal", 68 | "os", "posix", "pwd", "select", "signal", "_sre", "StringIO", 69 | "strop", "_struct", "sys", "_warnings", "warnings", "_weakref"] 70 | if stdlib: 71 | for mod in modules: 72 | builtins.modules[mod] = p.Parse(_FindBuiltinFile(mod + ".pytd")) 73 | _cached_builtins[cache_key] = builtins 74 | return builtins 75 | 76 | 77 | def GetBuiltinsHierarchy(): 78 | builtins = GetBuiltins() 79 | return builtins.Visit(visitors.ExtractSuperClassesByName()) 80 | 81 | 82 | # TODO: memoize (like GetBuiltins) 83 | def ParseBuiltinsFile(filename): 84 | """GetBuiltins(), but for a single file, not adding to builtins.modules. 85 | 86 | Only used in tests, e.g. for loading reduced or specialized builtin files. 87 | 88 | Args: 89 | filename: Filename, relative to pytypedecl/builtins/ 90 | Returns: 91 | A PyTypeDeclUnit for a single module. 92 | """ 93 | return parser.parse_string(_FindBuiltinFile(filename)) 94 | -------------------------------------------------------------------------------- /parse/utils_test.py: -------------------------------------------------------------------------------- 1 | """Tests for parse.utils.""" 2 | 3 | import unittest 4 | 5 | 6 | from pytypedecl import pytd 7 | from pytypedecl.parse import utils 8 | from pytypedecl.parse import visitors 9 | 10 | 11 | class UtilsTest(unittest.TestCase): 12 | 13 | @classmethod 14 | def setUpClass(cls): 15 | cls.builtins = utils.GetBuiltins() 16 | 17 | def testGetBuiltins(self): 18 | self.assertIsNotNone(self.builtins) 19 | self.assertTrue(hasattr(self.builtins, "modules")) 20 | # Will throw an error for unresolved identifiers: 21 | visitors.LookupClasses(self.builtins) 22 | 23 | def testHasMutableParameters(self): 24 | append = self.builtins.Lookup("list").Lookup("append") 25 | self.assertIsInstance(append.signatures[0].params[0], pytd.MutableParameter) 26 | 27 | def testHasCorrectSelf(self): 28 | update = self.builtins.Lookup("dict").Lookup("update") 29 | t = update.signatures[0].params[0].type 30 | self.assertIsInstance(t, pytd.GenericType) 31 | self.assertEquals(t.base_type, pytd.NamedType("dict")) 32 | 33 | def testHasObjectSuperClass(self): 34 | cls = self.builtins.Lookup("int") 35 | self.assertEquals(cls.parents, (pytd.NamedType("object"),)) 36 | cls = self.builtins.Lookup("object") 37 | self.assertEquals(cls.parents, ()) 38 | 39 | 40 | if __name__ == "__main__": 41 | unittest.main() 42 | -------------------------------------------------------------------------------- /parse/visitors_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | # Copyright 2013 Google Inc. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | 17 | import re 18 | import sys 19 | import textwrap 20 | import unittest 21 | 22 | from pytypedecl import pytd 23 | from pytypedecl.parse import parser_test 24 | from pytypedecl.parse import visitors 25 | 26 | 27 | # All of these tests implicitly test pytd.Print because 28 | # parser_test.AssertSourceEquals() uses pytd.Print. 29 | 30 | 31 | class TestVisitors(parser_test.ParserTest): 32 | """Tests the classes in parse/visitors.""" 33 | 34 | def testLookupClasses(self): 35 | src = textwrap.dedent(""" 36 | class object: 37 | pass 38 | 39 | class A: 40 | def a(self, a: A, b: B) -> A or B raises A, B 41 | 42 | class B: 43 | def b(self, a: A, b: B) -> A or B raises A, B 44 | """) 45 | tree = self.Parse(src) 46 | new_tree = visitors.LookupClasses(tree) 47 | self.AssertSourceEquals(new_tree, src) 48 | new_tree.Visit(visitors.VerifyLookup()) 49 | 50 | def testMaybeFillInClasses(self): 51 | src = textwrap.dedent(""" 52 | class A: 53 | def a(self, a: A, b: B) -> A or B raises A, B 54 | """) 55 | tree = self.Parse(src) 56 | ty_a = pytd.ClassType("A") 57 | visitors.FillInClasses(ty_a, tree) 58 | self.assertIsNotNone(ty_a.cls) 59 | ty_b = pytd.ClassType("B") 60 | visitors.FillInClasses(ty_b, tree) 61 | self.assertIsNone(ty_b.cls) 62 | 63 | def testReplaceTypes(self): 64 | src = textwrap.dedent(""" 65 | class A: 66 | def a(self, a: A or B) -> A or B raises A, B 67 | """) 68 | expected = textwrap.dedent(""" 69 | class A: 70 | def a(self: A2, a: A2 or B) -> A2 or B raises A2, B 71 | """) 72 | tree = self.Parse(src) 73 | new_tree = tree.Visit(visitors.ReplaceTypes({"A": pytd.NamedType("A2")})) 74 | self.AssertSourceEquals(new_tree, expected) 75 | 76 | def testSuperClassesByName(self): 77 | src = textwrap.dedent(""" 78 | class A(nothing): 79 | pass 80 | class B(nothing): 81 | pass 82 | class C(A): 83 | pass 84 | class D(A,B): 85 | pass 86 | class E(C,D,A): 87 | pass 88 | """) 89 | tree = self.Parse(src) 90 | data = tree.Visit(visitors.ExtractSuperClassesByName()) 91 | self.assertItemsEqual((), data["A"]) 92 | self.assertItemsEqual((), data["B"]) 93 | self.assertItemsEqual(("A",), data["C"]) 94 | self.assertItemsEqual(("A", "B"), data["D"]) 95 | self.assertItemsEqual(("A", "C", "D"), data["E"]) 96 | 97 | def testSuperClasses(self): 98 | src = textwrap.dedent(""" 99 | class A(nothing): 100 | pass 101 | class B(nothing): 102 | pass 103 | class C(A): 104 | pass 105 | class D(A,B): 106 | pass 107 | class E(C,D,A): 108 | pass 109 | """) 110 | ast = visitors.LookupClasses(self.Parse(src)) 111 | data = ast.Visit(visitors.ExtractSuperClasses()) 112 | self.assertItemsEqual([], [t.name for t in data[ast.Lookup("A")]]) 113 | self.assertItemsEqual([], [t.name for t in data[ast.Lookup("B")]]) 114 | self.assertItemsEqual(["A"], [t.name for t in data[ast.Lookup("C")]]) 115 | self.assertItemsEqual(["A", "B"], [t.name for t in data[ast.Lookup("D")]]) 116 | self.assertItemsEqual(["C", "D", "A"], 117 | [t.name for t in data[ast.Lookup("E")]]) 118 | 119 | def testInstantiateTemplates(self): 120 | src = textwrap.dedent(""" 121 | def foo(x: int) -> A 122 | 123 | class A: 124 | def foo(a: T) -> T raises T 125 | """) 126 | expected = textwrap.dedent(""" 127 | def foo(x: int) -> `A` 128 | 129 | class `A`: 130 | def foo(a: int) -> int raises int 131 | """) 132 | tree = self.Parse(src) 133 | new_tree = visitors.InstantiateTemplates(tree) 134 | self.AssertSourceEquals(new_tree, expected) 135 | 136 | def testInstantiateTemplatesWithParameters(self): 137 | src = textwrap.dedent(""" 138 | def foo(x: int) -> T1 139 | def foo(x: int) -> T2 140 | 141 | class T1: 142 | def foo(a: A) -> A raises A 143 | 144 | class T2: 145 | def foo(a: A) -> B raises B 146 | """) 147 | expected = textwrap.dedent(""" 148 | def foo(x: int) -> `T1` 149 | def foo(x: int) -> `T2` 150 | 151 | class `T1`: 152 | def foo(a: float) -> float raises float 153 | 154 | class `T2`: 155 | def foo(a: int) -> complex raises complex 156 | """) 157 | tree = self.Parse(src) 158 | new_tree = visitors.InstantiateTemplates(tree) 159 | self.AssertSourceEquals(new_tree, expected) 160 | 161 | def testStripSelf(self): 162 | src = textwrap.dedent(""" 163 | def add(x: int, y: int) -> int 164 | class A: 165 | def bar(self, x: int) -> float 166 | def baz(self) -> float 167 | def foo(self, x: int, y: float) -> float 168 | """) 169 | expected = textwrap.dedent(""" 170 | def add(x: int, y: int) -> int 171 | 172 | class A: 173 | def bar(x: int) -> float 174 | def baz() -> float 175 | def foo(x: int, y: float) -> float 176 | """) 177 | tree = self.Parse(src) 178 | new_tree = tree.Visit(visitors.StripSelf()) 179 | self.AssertSourceEquals(new_tree, expected) 180 | 181 | def testRemoveUnknownClasses(self): 182 | src = textwrap.dedent(""" 183 | class `~unknown1`(nothing): 184 | pass 185 | class `~unknown2`(nothing): 186 | pass 187 | class A: 188 | def foobar(x: `~unknown1`, y: `~unknown2`) -> `~unknown1` or int 189 | """) 190 | expected = textwrap.dedent(""" 191 | class A: 192 | def foobar(x: ?, y: ?) -> ? or int 193 | """) 194 | tree = self.Parse(src) 195 | tree = tree.Visit(visitors.RemoveUnknownClasses()) 196 | self.AssertSourceEquals(tree, expected) 197 | 198 | def testFindUnknownVisitor(self): 199 | src = textwrap.dedent(""" 200 | class `~unknown1`(nothing): 201 | pass 202 | class `~unknown_foobar`(nothing): 203 | pass 204 | class `~int`(nothing): 205 | pass 206 | class A(nothing): 207 | def foobar(self, x: `~unknown1`) -> ? 208 | class B(nothing): 209 | def foobar(self, x: `~int`) -> ? 210 | class C(nothing): 211 | x: `~unknown_foobar` 212 | class D(`~unknown1`): 213 | pass 214 | """) 215 | tree = self.Parse(src) 216 | tree = visitors.LookupClasses(tree) 217 | find_on = lambda x: tree.Lookup(x).Visit(visitors.RaiseIfContainsUnknown()) 218 | self.assertRaises(visitors.RaiseIfContainsUnknown.HasUnknown, find_on, "A") 219 | find_on("B") # shouldn't raise 220 | self.assertRaises(visitors.RaiseIfContainsUnknown.HasUnknown, find_on, "C") 221 | self.assertRaises(visitors.RaiseIfContainsUnknown.HasUnknown, find_on, "D") 222 | 223 | def testCanonicalOrderingVisitor(self): 224 | src1 = textwrap.dedent(""" 225 | def f(x: list) -> ? 226 | def f(x: list) -> ? 227 | def f(x: list>) -> ? 228 | """) 229 | src2 = textwrap.dedent(""" 230 | def f(x: list>) -> ? 231 | def f(x: list) -> ? 232 | def f(x: list) -> ? 233 | """) 234 | tree1 = self.Parse(src1) 235 | tree1 = tree1.Visit(visitors.CanonicalOrderingVisitor(sort_signatures=True)) 236 | tree2 = self.Parse(src2) 237 | tree2 = tree2.Visit(visitors.CanonicalOrderingVisitor(sort_signatures=True)) 238 | self.AssertSourceEquals(tree1, tree2) 239 | 240 | if __name__ == "__main__": 241 | unittest.main() 242 | -------------------------------------------------------------------------------- /pytd.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Our way of using namedtuple is confusing pylint. 18 | # pylint: disable=no-member 19 | 20 | """AST representation of a pytd file.""" 21 | 22 | 23 | import itertools 24 | import re 25 | from pytypedecl.parse import node 26 | 27 | 28 | # TODO(ampere): Add __new__ to Type subclasses that contain sequences to 29 | # convert arguments to tuples? 30 | 31 | 32 | class TypeDeclUnit(node.Node('name', 'constants', 'classes', 'functions', 33 | 'modules')): 34 | """Module node. Holds module contents (classes / functions) and submodules. 35 | 36 | Attributes: 37 | name: Name of this module, or None for the top-level module. 38 | constants: Iterable of module-level constants. 39 | functions: Iterable of functions defined in this type decl unit. 40 | classes: Iterable of classes defined in this type decl unit. 41 | modules: Iterable of submodules of the current module. 42 | """ 43 | __slots__ = () 44 | 45 | def Lookup(self, name): 46 | """Convenience function: Look up a given name in the global namespace. 47 | 48 | Tries to find a constant, function or class by this name. 49 | 50 | Args: 51 | name: Name to look up. 52 | 53 | Returns: 54 | A Constant, Function or Class. 55 | 56 | Raises: 57 | KeyError: if this identifier doesn't exist. 58 | """ 59 | # TODO: Remove. Change constants, classes and functions to dict. 60 | try: 61 | return self._name2item[name] 62 | except AttributeError: 63 | self._name2item = {} 64 | for x in self.constants + self.functions + self.classes + self.modules: 65 | self._name2item[x.name] = x 66 | return self._name2item[name] 67 | 68 | def __hash__(self): 69 | return id(self) 70 | 71 | def __eq__(self, other): 72 | return id(self) == id(other) 73 | 74 | def __ne__(self, other): 75 | return id(self) != id(other) 76 | 77 | 78 | class Constant(node.Node('name', 'type')): 79 | __slots__ = () 80 | 81 | 82 | class Class(node.Node('name', 'parents', 'methods', 'constants', 'template')): 83 | """Represents a class declaration. 84 | 85 | Used as dict/set key, so all components must be hashable. 86 | 87 | Attributes: 88 | name: Class name (string) 89 | parents: The super classes of this class (instances of Type). 90 | methods: Tuple of class methods (instances of Function). 91 | constants: Tuple of constant class attributes (instances of Constant). 92 | template: Tuple of TemplateItem instances. 93 | """ 94 | # TODO: Rename "parents" to "bases". "Parents" is confusing since we're 95 | # in a tree. 96 | 97 | __slots__ = () 98 | 99 | def Lookup(self, name): 100 | """Convenience function: Look up a given name in the class namespace. 101 | 102 | Tries to find a method or constant by this name in the class. 103 | 104 | Args: 105 | name: Name to look up. 106 | 107 | Returns: 108 | A Constant or Function instance. 109 | 110 | Raises: 111 | KeyError: if this identifier doesn't exist in this class. 112 | """ 113 | # TODO: Remove this. Make methods and constants dictionaries. 114 | try: 115 | return self._name2item[name] 116 | except AttributeError: 117 | self._name2item = {} 118 | for x in self.methods + self.constants: 119 | self._name2item[x.name] = x 120 | return self._name2item[name] 121 | 122 | 123 | class Function(node.Node('name', 'signatures')): 124 | """A function or a method. 125 | 126 | Attributes: 127 | name: The name of this function. 128 | signatures: Possible list of parameter type combinations for this function. 129 | """ 130 | __slots__ = () 131 | 132 | 133 | class Signature(node.Node('params', 'return_type', 'exceptions', 'template', 134 | 'has_optional')): 135 | """Represents an individual signature of a function. 136 | 137 | For overloaded functions, this is one specific combination of parameters. 138 | For non-overloaded functions, there is a 1:1 correspondence between function 139 | and signature. 140 | 141 | Attributes: 142 | name: The name of this function. 143 | params: The list of parameters for this function definition. 144 | return_type: The return type of this function. 145 | exceptions: List of exceptions for this function definition. 146 | template: names for bindings for bounded types in params/return_type 147 | has_optional: Do we have optional parameters ("...")? 148 | """ 149 | __slots__ = () 150 | 151 | 152 | class Parameter(node.Node('name', 'type')): 153 | """Represents a parameter of a function definition. 154 | 155 | Attributes: 156 | name: The name of the parameter. 157 | type: The type of the parameter. 158 | """ 159 | __slots__ = () 160 | 161 | 162 | # Conceptually, this is a subtype of Parameter: 163 | class MutableParameter(node.Node('name', 'type', 'new_type')): 164 | """Represents a parameter that's modified by the function. 165 | 166 | Attributes: 167 | name: The name of the parameter. 168 | type: The type of the parameter. 169 | new_type: The type the parameter will have after the function is called. 170 | """ 171 | __slots__ = () 172 | 173 | 174 | class TypeParameter(node.Node('name')): 175 | """Represents a type parameter. 176 | 177 | A type parameter is a bound variable in the context of a function or class 178 | definition. It specifies an equivalence between types. 179 | For example, this defines a identity function: 180 | def f(x: T) -> T 181 | """ 182 | __slots__ = () 183 | 184 | 185 | class TemplateItem(node.Node('type_param', 'within_type')): 186 | """Represents "template name extends bounded_type". 187 | 188 | This is used for classes and signatures. The 'template' field of both is 189 | a list of TemplateItems. Note that *using* the template happens through 190 | TypeParameters. E.g. in: 191 | class A: 192 | def f(T x) -> T 193 | both the "T"s in the definition of f() are using pytd.TypeParameter to refer 194 | to the TemplateItem in class A's template. 195 | 196 | Attributes: 197 | type_param: the TypeParameter instance used. This is the actual instance 198 | that's used wherever this type parameter appears, e.g. within a class. 199 | within_type: the "extends" type for this name (e.g., NamedType('object')) 200 | """ 201 | __slots__ = () 202 | 203 | @property 204 | def name(self): 205 | return self.type_param.name 206 | 207 | 208 | # Types can be: 209 | # 1.) NamedType: 210 | # Specifies a type by name (i.e., a string) 211 | # 2.) NativeType 212 | # Points to a Python type. (int, float etc.) 213 | # 3.) ClassType 214 | # Points back to a Class in the AST. (This makes the AST circular) 215 | # 4.) GenericType 216 | # Contains a base type and parameters. 217 | # 5.) UnionType / IntersectionType 218 | # Can be multiple types at once. 219 | # 6.) NothingType / AnythingType 220 | # Special purpose types that represent nothing or everything. 221 | # 7.) TypeParameter 222 | # A placeholder for a type. 223 | # 8.) Scalar 224 | # A singleton type. Not currently used, but supported by the parser. 225 | # For 1-3, the file visitors.py contains tools for converting between the 226 | # corresponding AST representations. 227 | 228 | 229 | class NamedType(node.Node('name')): 230 | """A type specified by name.""" 231 | __slots__ = () 232 | 233 | def __str__(self): 234 | return str(self.name) 235 | 236 | 237 | class NativeType(node.Node('python_type')): 238 | """A type specified by a native Python type. Used during runtime checking.""" 239 | __slots__ = () 240 | 241 | 242 | class ClassType(node.Node('name')): 243 | """A type specified through an existing class node.""" 244 | 245 | # This type is different from normal nodes: 246 | # (a) It's mutable, and there are functions (parse/visitors.py:FillInClasses) 247 | # that modify a tree in place. 248 | # (b) Because it's mutable, it's not actually using the tuple/Node interface 249 | # to store things (in particular, the pointer to the existing class). 250 | # (c) Visitors will not process the "children" of this node. Since we point 251 | # to classes that are back at the top of the tree, that would generate 252 | # cycles. 253 | 254 | __slots__ = () 255 | 256 | def __new__(cls, name, clsref=None): 257 | self = super(ClassType, cls).__new__(cls, name) 258 | self.cls = clsref # potentially filled in later (by visitors.FillInClasses) 259 | return self 260 | 261 | # __eq__ is inherited (using tuple equality + requiring the two classes 262 | # be the same) 263 | 264 | def __str__(self): 265 | return str(self.cls.name) if self.cls else self.name 266 | 267 | def __repr__(self): 268 | return '{type}{cls}({name})'.format( 269 | type=type(self).__name__, name=self.name, 270 | cls='' if self.cls is None else '') 271 | 272 | 273 | class AnythingType(node.Node()): 274 | """A type we know nothing about yet ('?' in pytd).""" 275 | __slots__ = () 276 | 277 | 278 | class NothingType(node.Node()): 279 | """An "impossible" type, with no instances ('nothing' in pytd). 280 | 281 | Also known as the "uninhabited" type. For representing empty lists, and 282 | functions that never return. 283 | """ 284 | __slots__ = () 285 | 286 | 287 | class Scalar(node.Node('value')): 288 | __slots__ = () 289 | 290 | 291 | class UnionType(node.Node('type_list')): 292 | """A union type that contains all types in self.type_list.""" 293 | __slots__ = () 294 | 295 | # NOTE: type_list is kept as a tuple, to preserve the original order 296 | # even though in most respects it acts like a frozenset. 297 | # It also flattens the input, such that printing without 298 | # parentheses gives the same result. 299 | 300 | def __new__(cls, type_list): 301 | assert type_list # Disallow empty unions. Use NothingType for these. 302 | flattened = itertools.chain.from_iterable( 303 | t.type_list if isinstance(t, UnionType) else [t] for t in type_list) 304 | return super(UnionType, cls).__new__(cls, tuple(flattened)) 305 | 306 | def __hash__(self): 307 | # See __eq__ - order doesn't matter, so use frozenset 308 | return hash(frozenset(self.type_list)) 309 | 310 | def __eq__(self, other): 311 | if self is other: 312 | return True 313 | if isinstance(other, UnionType): 314 | # equality doesn't care about the ordering of the type_list 315 | return frozenset(self.type_list) == frozenset(other.type_list) 316 | return NotImplemented 317 | 318 | def __ne__(self, other): 319 | return not self == other 320 | 321 | 322 | # TODO: Do we still need this? 323 | class IntersectionType(node.Node('type_list')): 324 | """An intersection type that contains all types in self.type_list.""" 325 | __slots__ = () 326 | 327 | # NOTE: type_list is kept as a tuple, to preserve the original order 328 | # even though in most respects it acts like a frozenset. 329 | # It also flattens the input, such that printing without 330 | # parentheses gives the same result. 331 | 332 | def __new__(cls, type_list): 333 | flattened = itertools.chain.from_iterable( 334 | t.type_list if isinstance(t, IntersectionType) else [t] 335 | for t in type_list) 336 | return super(IntersectionType, cls).__new__(cls, tuple(flattened)) 337 | 338 | def __hash__(self): 339 | # See __eq__ - order doesn't matter, so use frozenset 340 | return hash(frozenset(self.type_list)) 341 | 342 | def __eq__(self, other): 343 | if self is other: 344 | return True 345 | if isinstance(other, IntersectionType): 346 | # equality doesn't care about the ordering of the type_list 347 | return frozenset(self.type_list) == frozenset(other.type_list) 348 | return NotImplemented 349 | 350 | def __ne__(self, other): 351 | return not self == other 352 | 353 | 354 | class GenericType(node.Node('base_type', 'parameters')): 355 | """Generic type. Takes a base type and type paramters. 356 | 357 | This corresponds to the syntax: type, type (etc.). 358 | 359 | Attributes: 360 | base_type: The base type. Instance of Type. 361 | parameters: Type paramters. Tuple of instances of Type. 362 | """ 363 | __slots__ = () 364 | 365 | 366 | class HomogeneousContainerType(GenericType): 367 | """Special generic type for homogeneous containers. Only has one type param. 368 | 369 | This differs from GenericType in that it assumes *all* items in a container 370 | will be the same type. The syntax is type. (Vs type for GenericType.) 371 | """ 372 | __slots__ = () 373 | 374 | @property 375 | def element_type(self): 376 | return self.parameters[0] 377 | 378 | 379 | # So we can do "isinstance(node, pytd.TYPE)": 380 | TYPE = (NamedType, NativeType, ClassType, AnythingType, UnionType, 381 | NothingType, GenericType, TypeParameter, Scalar, 382 | IntersectionType, Scalar) 383 | 384 | 385 | def Print(n): 386 | """Convert a PYTD node to a string.""" 387 | # TODO: fix circular import 388 | from pytypedecl.parse import visitors 389 | res = n.Visit(visitors.PrintVisitor()) 390 | # Remove trailing blanks on lines (*not* \s which includes \n) -- these come 391 | # from indents that have no other code on them. 392 | return re.sub(r" +\n", "\n", res) 393 | -------------------------------------------------------------------------------- /pytd_test.py: -------------------------------------------------------------------------------- 1 | """Tests for pytd.""" 2 | 3 | import itertools 4 | import unittest 5 | from pytypedecl import pytd 6 | 7 | 8 | class TestPytd(unittest.TestCase): 9 | """Test the simple functionality in pytd.py.""" 10 | 11 | def setUp(self): 12 | self.int = pytd.ClassType("int") 13 | self.none_type = pytd.ClassType("NoneType") 14 | self.float = pytd.ClassType("float") 15 | self.list = pytd.ClassType("list") 16 | 17 | def testUnionTypeEq(self): 18 | u1 = pytd.UnionType((self.int, self.float)) 19 | u2 = pytd.UnionType((self.float, self.int)) 20 | self.assertEqual(u1, u2) 21 | self.assertEqual(u2, u1) 22 | self.assertEqual(u1.type_list, (self.int, self.float)) 23 | self.assertEqual(u2.type_list, (self.float, self.int)) 24 | 25 | def testUnionTypeNe(self): 26 | u1 = pytd.UnionType((self.int, self.float)) 27 | u2 = pytd.UnionType((self.float, self.int, self.none_type)) 28 | self.assertNotEqual(u1, u2) 29 | self.assertNotEqual(u2, u1) 30 | self.assertEqual(u1.type_list, (self.int, self.float)) 31 | self.assertEqual(u2.type_list, (self.float, self.int, self.none_type)) 32 | 33 | def testOrder(self): 34 | # pytd types' primary sort key is the class name, second sort key is 35 | # the contents when interpreted as a (named)tuple. 36 | nodes = [pytd.AnythingType(), 37 | pytd.GenericType(self.list, (self.int,)), 38 | pytd.NamedType("int"), 39 | pytd.NothingType(), 40 | pytd.UnionType(self.float), 41 | pytd.UnionType(self.int)] 42 | for n1, n2 in zip(nodes[:-1], nodes[1:]): 43 | self.assertLess(n1, n2) 44 | self.assertLessEqual(n1, n2) 45 | self.assertGreater(n2, n1) 46 | self.assertGreaterEqual(n2, n1) 47 | for p in itertools.permutations(nodes): 48 | self.assertEquals(list(sorted(p)), nodes) 49 | 50 | if __name__ == "__main__": 51 | unittest.main() 52 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | """Distutils-based script to build and install pytypedecl. 18 | 19 | Run 20 | python setup.py build 21 | to build and 22 | python setup.py install 23 | to install. 24 | """ 25 | 26 | from distutils.core import setup 27 | 28 | setup(name='pytypedecl', 29 | version='0.1', 30 | description='Runtime type checking', 31 | url='http://www.github.com/google/pytypedecl', 32 | requires=['ply(>=3.0)'], 33 | package_dir={'pytypedecl': ''}, 34 | packages=['pytypedecl', 'pytypedecl.parse'], 35 | ) 36 | -------------------------------------------------------------------------------- /slots.py: -------------------------------------------------------------------------------- 1 | """Mapping between slot / operator names. 2 | 3 | This defines the internal constants CPython uses to map magic methods to slots 4 | of PyTypeObject structures, and also other constants, like compare operator 5 | mappings. 6 | """ 7 | 8 | 9 | import collections 10 | 11 | 12 | TYPEOBJECT_PREFIX = "tp_" 13 | NUMBER_PREFIX = "nb_" 14 | SEQUENCE_PREFIX = "sq_" 15 | MAPPING_PREFIX = "mp_" 16 | 17 | 18 | class Slot(object): 19 | """A "slot" describes a Python operator. 20 | 21 | In particular, it describes how a magic method (E.g. "__add__") relates to the 22 | opcode ("BINARY_ADD") and the C function pointer ("nb_add"). 23 | 24 | Args: 25 | python_name: The name of the Python method. E.g. "__add__". 26 | c_name: The name of the C function pointer. Only use the base name, e.g. 27 | for tp_as_number->nb_add, use nb_add. 28 | function_type: Type of the C function 29 | index: If multiple python methods share the same function pointer 30 | (e.g. __add__ and __radd__), this is 0 or 1. 31 | opcode: The name of the opcode that CPython uses to call this 32 | function. This is only filled in for operators (e.g. BINARY_SUBSCR), 33 | but not for operations (e.g. STORE_SUBSCR). 34 | python_version: "2", "3", or (default) "*" 35 | """ 36 | 37 | def __init__(self, python_name, c_name, function_type, index=None, 38 | opcode=None, python_version="*"): 39 | self.python_name = python_name 40 | self.c_name = c_name 41 | self.function_type = function_type 42 | self.index = index 43 | self.opcode = opcode 44 | self.python_version = python_version 45 | 46 | 47 | SLOTS = [ 48 | # typeobject 49 | Slot("__new__", "tp_new", "new"), 50 | Slot("__init__", "tp_init", "init"), 51 | Slot("__str__", "tp_print", "print"), 52 | Slot("__repr__", "tp_repr", "repr", 53 | opcode="UNARY_CONVERT"), 54 | 55 | Slot("__hash__", "tp_hash", "hash"), 56 | Slot("__call__", "tp_call", "call"), 57 | 58 | # Note: In CPython, if tp_getattro exists, tp_getattr is never called. 59 | Slot("__getattribute__", "tp_getattro", "getattro"), 60 | Slot("__getattr__", "tp_getattro", "getattro"), 61 | Slot("__setattr__", "tp_setattro", "setattro"), 62 | Slot("__delattr__", "tp_setattro", "setattro"), 63 | 64 | # for Py_TPFLAGS_HAVE_ITER: 65 | Slot("__iter__", "tp_iter", "unary"), 66 | Slot("next", "tp_iternext", "next", python_version="2"), 67 | Slot("__next__", "tp_iternext", "next", python_version="3"), 68 | 69 | # for Py_TPFLAGS_HAVE_CLASS: 70 | Slot("__get__", "tp_descr_get", "descr_get"), 71 | Slot("__set__", "tp_descr_set", "descr_set"), 72 | Slot("__delete__", "tp_descr_set", "descr_delete"), 73 | Slot("__del__", "tp_del", "destructor"), 74 | 75 | # all typically done by __richcompare__ 76 | Slot("__cmp__", "tp_compare", "cmp", 77 | python_version="2"), # "tp_reserved" in Python 3 78 | Slot("__lt__", "tp_richcompare", "richcmpfunc"), 79 | Slot("__le__", "tp_richcompare", "richcmpfunc"), 80 | Slot("__eq__", "tp_richcompare", "richcmpfunc"), 81 | Slot("__ne__", "tp_richcompare", "richcmpfunc"), 82 | Slot("__gt__", "tp_richcompare", "richcmpfunc"), 83 | Slot("__ge__", "tp_richcompare", "richcmpfunc"), 84 | 85 | Slot("__richcompare__", "tp_richcompare", "richcmpfunc"), 86 | 87 | # number methods: 88 | Slot("__add__", "nb_add", "binary_nb", index=0, 89 | opcode="BINARY_ADD"), 90 | Slot("__radd__", "nb_add", "binary_nb", index=1), 91 | Slot("__sub__", "nb_subtract", "binary_nb", index=0, 92 | opcode="BINARY_SUBTRACT"), 93 | Slot("__rsub__", "nb_subtract", "binary_nb", index=1), 94 | Slot("__mul__", "nb_multiply", "binary_nb", index=0), 95 | Slot("__rmul__", "nb_multiply", "binary_nb", index=1), 96 | Slot("__div__", "nb_divide", "binary_nb", index=0, 97 | opcode="BINARY_DIVIDE"), 98 | Slot("__rdiv__", "nb_divide", "binary_nb", index=1), 99 | Slot("__mod__", "nb_remainder", "binary_nb", index=0, 100 | opcode="BINARY_MODULO"), 101 | Slot("__rmod__", "nb_remainder", "binary_nb", index=1), 102 | Slot("__divmod__", "nb_divmod", "binary_nb", index=0), 103 | Slot("__rdivmod__", "nb_divmod", "binary_nb", index=1), 104 | Slot("__lshift__", "nb_lshift", "binary_nb", index=0, 105 | opcode="BINARY_LSHIFT"), 106 | Slot("__rlshift__", "nb_lshift", "binary_nb", index=1), 107 | Slot("__rshift__", "nb_rshift", "binary_nb", index=0, 108 | opcode="BINARY_RSHIFT"), 109 | Slot("__rrshift__", "nb_rshift", "binary_nb", index=1), 110 | Slot("__and__", "nb_and", "binary_nb", index=0, 111 | opcode="BINARY_AND"), 112 | Slot("__rand__", "nb_and", "binary_nb", index=1), 113 | Slot("__xor__", "nb_xor", "binary_nb", index=0, 114 | opcode="BINARY_XOR"), 115 | Slot("__rxor__", "nb_xor", "binary_nb", index=1), 116 | Slot("__or__", "nb_or", "binary_nb", index=0, 117 | opcode="BINARY_OR"), 118 | Slot("__ror__", "nb_or", "binary_nb", index=1), 119 | # needs Py_TPFLAGS_HAVE_CLASS: 120 | Slot("__floordiv__", "nb_floor_divide", "binary_nb", index=0, 121 | opcode="BINARY_FLOOR_DIVIDE"), 122 | Slot("__rfloordiv__", "nb_floor_divide", "binary_nb", index=1), 123 | Slot("__truediv__", "nb_true_divide", "binary_nb", index=0, 124 | opcode="BINARY_TRUE_DIVIDE"), 125 | Slot("__rtruediv__", "nb_true_divide", "binary_nb", index=1), 126 | 127 | Slot("__pow__", "nb_power", "ternary", 128 | opcode="BINARY_POWER"), 129 | Slot("__rpow__", "nb_power", "ternary"), # needs wrap_tenary_nb 130 | 131 | Slot("__neg__", "nb_negative", "unary", 132 | opcode="UNARY_NEGATIVE"), 133 | Slot("__pos__", "nb_positive", "unary", 134 | opcode="UNARY_POSITIVE"), 135 | Slot("__abs__", "nb_absolute", "unary"), 136 | Slot("__nonzero__", "nb_nonzero", "inquiry"), # inverse of UNARY_NOT opcode 137 | Slot("__invert__", "nb_invert", "unary", 138 | opcode="UNARY_INVERT"), 139 | Slot("__coerce__", "nb_coerce", "coercion"), # not needed 140 | Slot("__int__", "nb_int", "unary"), # expects exact int as return 141 | Slot("__long__", "nb_long", "unary"), # expects exact long as return 142 | Slot("__float__", "nb_float", "unary"), # expects exact float as return 143 | Slot("__oct__", "nb_oct", "unary"), 144 | Slot("__hex__", "nb_hex", "unary"), 145 | 146 | # Added in 2.0. These are probably largely useless. 147 | # (For list concatenation, use sl_inplace_concat) 148 | Slot("__iadd__", "nb_inplace_add", "binary", 149 | opcode="INPLACE_ADD"), 150 | Slot("__isub__", "nb_inplace_subtract", "binary", 151 | opcode="INPLACE_SUBTRACT"), 152 | Slot("__imul__", "nb_inplace_multiply", "binary", 153 | opcode="INPLACE_MULTIPLY"), 154 | Slot("__idiv__", "nb_inplace_divide", "binary", 155 | opcode="INPLACE_DIVIDE"), 156 | Slot("__imod__", "nb_inplace_remainder", "binary", 157 | opcode="INPLACE_MODULO"), 158 | Slot("__ipow__", "nb_inplace_power", "ternary", 159 | opcode="INPLACE_POWER"), 160 | Slot("__ilshift__", "nb_inplace_lshift", "binary", 161 | opcode="INPLACE_LSHIFT"), 162 | Slot("__irshift__", "nb_inplace_rshift", "binary", 163 | opcode="INPLACE_RSHIFT"), 164 | Slot("__iand__", "nb_inplace_and", "binary", 165 | opcode="INPLACE_AND"), 166 | Slot("__ixor__", "nb_inplace_xor", "binary", 167 | opcode="INPLACE_XOR"), 168 | Slot("__ior__", "nb_inplace_or", "binary", 169 | opcode="INPLACE_OR"), 170 | Slot("__ifloordiv__", "nb_inplace_floor_divide", "binary", 171 | opcode="INPLACE_FLOOR_DIVIDE"), 172 | Slot("__itruediv__", "nb_inplace_true_divide", "binary", 173 | opcode="INPLACE_TRUE_DIVIDE"), 174 | 175 | # Added in 2.5. Used whenever i acts as a sequence index (a[i]) 176 | Slot("__index__", "nb_index", "unary"), # needs int/long return 177 | 178 | # mapping 179 | # __getitem__: Python first tries mp_subscript, then sq_item 180 | # __len__: Python first tries sq_length, then mp_length 181 | # __delitem__: Reuses __setitem__ slot. 182 | Slot("__getitem__", "mp_subscript", "binary", 183 | opcode="BINARY_SUBSCR"), 184 | Slot("__delitem__", "mp_ass_subscript", "objobjargproc", index=0), 185 | Slot("__setitem__", "mp_ass_subscript", "objobjargproc", index=1), 186 | Slot("__len__", "mp_length", "len"), 187 | 188 | # sequence 189 | Slot("__contains__", "sq_contains", "objobjproc"), 190 | 191 | # These sequence methods are duplicates of number or mapping methods. 192 | # For example, in the C API, "add" can be implemented either by sq_concat, 193 | # or by np_add. Python will try both. The opcode mapping is identical 194 | # between the two. So e.g. the implementation of the BINARY_SUBSCR opcode in 195 | # Python/ceval.c will try both sq_item and mp_subscript, which is why this 196 | # opcode appears twice in our list. 197 | Slot("__add__", "sq_concat", "binary", 198 | opcode="BINARY_ADD"), 199 | Slot("__mul__", "sq_repeat", "indexargfunc", 200 | opcode="BINARY_MULTIPLY"), 201 | Slot("__iadd__", "sq_inplace_concat", "binary", 202 | opcode="INPLACE_ADD"), 203 | Slot("__imul__", "sq_inplace_repeat", "indexargfunc", 204 | opcode="INPLACE_MUL"), 205 | Slot("__getitem__", "sq_item", "sq_item", 206 | opcode="BINARY_SUBSCR"), 207 | Slot("__setitem__", "sq_ass_slice", "sq_ass_item"), 208 | Slot("__delitem__", "sq_ass_item", "sq_delitem"), 209 | 210 | # slices are passed as explicit slice objects to mp_subscript. 211 | Slot("__getslice__", "sq_slice", "sq_slice"), 212 | Slot("__setslice__", "sq_ass_slice", "ssizessizeobjarg"), 213 | Slot("__delslice__", "sq_ass_slice", "delslice"), 214 | ] 215 | 216 | 217 | CompareOp = collections.namedtuple("CompareOp", ["op", "index", "magic"]) 218 | 219 | 220 | CMP_LT = 0 221 | CMP_LE = 1 222 | CMP_EQ = 2 223 | CMP_NE = 3 224 | CMP_GT = 4 225 | CMP_GE = 5 226 | CMP_IN = 6 227 | CMP_NOT_IN = 7 228 | CMP_IS = 8 229 | CMP_IS_NOT = 9 230 | CMP_EXC_MATCH = 10 231 | 232 | 233 | COMPARE_OPS = [ 234 | CompareOp("LT", CMP_LT, "__lt__"), 235 | CompareOp("LE", CMP_LE, "__le__"), 236 | CompareOp("EQ", CMP_EQ, "__eq__"), 237 | CompareOp("NE", CMP_NE, "__ne__"), 238 | CompareOp("GT", CMP_GT, "__gt__"), 239 | CompareOp("GE", CMP_GE, "__ge__"), 240 | CompareOp("IN", CMP_IN, None), # reversed __contains__ 241 | CompareOp("NOT_IN", CMP_NOT_IN, None), # reversed inverted __contains__ 242 | # these don't have a magic function: 243 | CompareOp("IS", CMP_IS, None), 244 | CompareOp("IS_NOT", CMP_IS_NOT, None), 245 | CompareOp("EXC_MATCH", CMP_EXC_MATCH, None), 246 | ] 247 | 248 | 249 | # Used by abstractvm.py: 250 | def GetBinaryOperatorMapping(): 251 | return _GetSlotMagicMapping("BINARY_") 252 | 253 | 254 | def GetInplaceOperatorMapping(): 255 | return _GetSlotMagicMapping("INPLACE_") 256 | 257 | 258 | def GetUnaryOperatorMapping(): 259 | return _GetSlotMagicMapping("UNARY_") 260 | 261 | 262 | def GetCompareFunctionMapping(): 263 | return {index: magic 264 | for op, index, magic in COMPARE_OPS 265 | if magic} 266 | 267 | 268 | def _GetSlotMagicMapping(prefix): 269 | return {slot.opcode[len(prefix):]: slot.python_name 270 | for slot in SLOTS 271 | if slot.opcode and slot.opcode.startswith(prefix)} 272 | -------------------------------------------------------------------------------- /slots_test.py: -------------------------------------------------------------------------------- 1 | """Tests for slots.py.""" 2 | 3 | import unittest 4 | from pytypedecl import slots 5 | 6 | 7 | class TestPytd(unittest.TestCase): 8 | """Test the operator mappings in slots.py.""" 9 | 10 | def testBinaryOperatorMapping(self): 11 | slots.GetBinaryOperatorMapping().get("ADD") # smoke test 12 | 13 | def testCompareFunctionMapping(self): 14 | indexes = slots.GetCompareFunctionMapping().keys() 15 | # Assert that we have the six basic comparison ops (<, <=, ==, !=, >, >=). 16 | for i in range(6): 17 | self.assertIn(i, indexes) 18 | 19 | 20 | if __name__ == "__main__": 21 | unittest.main() 22 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/pytypedecl/c7a1bdd9044b00b67e1fc21a48365e424eace0c0/tests/__init__.py -------------------------------------------------------------------------------- /tests/classes.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | """Used for tests.""" 19 | 20 | # pylint: disable=unused-argument 21 | # pylint: disable=unused-import 22 | # pylint: disable=g-line-too-long 23 | # pylint: disable=unused-variable 24 | 25 | import sys 26 | from pytypedecl import checker 27 | 28 | 29 | class Emailer(object): 30 | 31 | def MakeAnnouncement(self, emails): 32 | for addr in emails: 33 | self.SendEmail(addr) 34 | 35 | def SendEmail(self, addr): 36 | return "sending email to " + addr 37 | 38 | @classmethod 39 | def GetServerInfo(cls, port): 40 | return "smtp.server.com:" + str(port) 41 | 42 | 43 | class Utils(object): 44 | # Overloaded signatures 45 | # def Repeat(self, s: str, number: int) -> str 46 | # def Repeat(self, s: str, number: float) -> str 47 | 48 | def Repeat(self, s, number): 49 | return s * int(number) 50 | 51 | 52 | class Comparators(object): 53 | 54 | # def isGreater(a: int, b: int) -> bool 55 | 56 | @classmethod 57 | def IsGreater(cls, a, b): 58 | return a > b 59 | 60 | 61 | checker.CheckFromFile(sys.modules[__name__], __file__ + "td") 62 | -------------------------------------------------------------------------------- /tests/classes.pytd: -------------------------------------------------------------------------------- 1 | # -*- mode: python; coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | class Emailer: 19 | def MakeAnnouncement(self, emails: list) -> None 20 | def SendEmail(self, addr) -> str 21 | def GetServerInfo(cls, port: int) -> str 22 | 23 | class Utils: 24 | def Repeat(self, s: str, number: int) -> str 25 | def Repeat(self, s: str, number: float) -> str 26 | 27 | class Comparators: 28 | def IsGreater(cls, a: int, b: int) -> bool 29 | -------------------------------------------------------------------------------- /tests/generics.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | """Used for tests.""" 19 | 20 | # pylint: disable=unused-argument 21 | # pylint: disable=unused-import 22 | # pylint: disable=g-line-too-long 23 | # pylint: disable=unused-variable 24 | 25 | import sys 26 | from pytypedecl import checker 27 | 28 | 29 | # def Length(l : list) -> int 30 | def Length(l): 31 | return len(l) 32 | 33 | 34 | class Box(object): 35 | 36 | def __init__(self, data): 37 | self.data = data 38 | 39 | def Get(self): 40 | return self.data 41 | 42 | def __iter__(self): 43 | return iter([self.data]) 44 | 45 | 46 | # def UnwrapBox(b: Box) -> int 47 | def UnwrapBox(b): 48 | return b.Get() 49 | 50 | 51 | # def FindInCache(cache: dict, k: str) -> int 52 | def FindInCache(cache, k): 53 | return cache[k] 54 | 55 | 56 | # def _BadGen() -> generator 57 | def _BadGen(): 58 | """This is *supposed* to yield integers...""" 59 | for num in [1, 2, 3.1414926, 4]: 60 | yield num 61 | 62 | 63 | # def ConvertGenToList(g: generator) -> list 64 | def ConvertGenToList(g): 65 | return list(g) 66 | 67 | 68 | def ConsumeDoubleGenerator(g1, g2): 69 | 70 | l1 = [e for e in g1] 71 | l2 = [e for e in g2] 72 | return l2 73 | 74 | checker.CheckFromFile(sys.modules[__name__], __file__ + "td") 75 | -------------------------------------------------------------------------------- /tests/generics.pytd: -------------------------------------------------------------------------------- 1 | # -*- mode: python; coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | def Length(l : list) -> int 19 | def UnwrapBox(b: Box) -> int 20 | def _BadGen() -> generator 21 | def FindInCache(cache: dict, k: str) -> int 22 | def ConvertGenToList(g: generator) -> list 23 | def ConsumeDoubleGenerator(g1: generator, g2: generator) -> list 24 | -------------------------------------------------------------------------------- /tests/interface.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | """Used for tests.""" 19 | 20 | # pylint: disable=unused-argument 21 | # pylint: disable=unused-import 22 | 23 | import sys 24 | from pytypedecl import checker 25 | 26 | 27 | class FakeReadable(object): 28 | def Open(self): 29 | pass 30 | 31 | def Read(self): 32 | return "Hello" 33 | 34 | def Close(self): 35 | pass 36 | 37 | 38 | class NoGoodWritable(object): 39 | def Open(self): 40 | pass 41 | 42 | def Write(self): 43 | pass 44 | 45 | # missing Close 46 | 47 | 48 | class FakeOpenable(object): 49 | def Open(self): 50 | pass 51 | 52 | 53 | # def ReadStuff(r: ReadInterface) -> str 54 | def ReadStuff(r): 55 | r.Open() 56 | result = r.Read() 57 | r.Close() 58 | return result 59 | 60 | 61 | # def GetWritable() -> WriteInterface 62 | def GetWritable(): 63 | return NoGoodWritable() 64 | 65 | 66 | checker.CheckFromFile(sys.modules[__name__], __file__ + "td") 67 | -------------------------------------------------------------------------------- /tests/interface.pytd: -------------------------------------------------------------------------------- 1 | # -*- mode: python; coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | def ReadStuff(r : ReadInterface) -> str 19 | 20 | def GetWritable() -> WriteInterface 21 | 22 | # a few interfaces definitions 23 | # a comment for testing the lexer 24 | interface OpenInterface: 25 | def Open 26 | 27 | interface CloseInterface: 28 | def Close 29 | 30 | interface ReadInterface(OpenInterface, CloseInterface): 31 | def Read 32 | 33 | interface WriteInterface(OpenInterface, CloseInterface): 34 | def Write 35 | 36 | -------------------------------------------------------------------------------- /tests/overloading.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | """Used for tests.""" 19 | 20 | # pylint: disable=unused-argument 21 | 22 | import sys 23 | from pytypedecl import checker 24 | from tests import simple 25 | 26 | 27 | # def Bar(i :int) -> int 28 | # def Bar(i :str) -> int 29 | def Bar(i): 30 | return 42 31 | 32 | 33 | # def Fib(n: int) -> int 34 | # def Fib(n: float) -> int 35 | def Fib(n): 36 | if n == 1: 37 | return 1 38 | elif n == 2: 39 | return 2 40 | else: 41 | return Fib(n - 1) + Fib(n - 2) 42 | 43 | 44 | # def MultiOverload(a: int) -> int 45 | # def MultiOverload(a: float) -> float 46 | # def MultiOverload(a: str) -> str 47 | # def MultiOverload(a : list) -> list 48 | def MultiOverload(a): 49 | return a 50 | 51 | 52 | # def ExceptionOverload() -> None raise foo.WrongException 53 | # def ExceptionOverload() -> None raise foo.BadException 54 | def ExceptionOverload(): 55 | raise simple.WrongException 56 | 57 | checker.CheckFromFile(sys.modules[__name__], __file__ + "td") 58 | -------------------------------------------------------------------------------- /tests/overloading.pytd: -------------------------------------------------------------------------------- 1 | # -*- mode: python; coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | def Bar(i: int) -> int 19 | def Bar(i: str) -> int 20 | def Fib(n: int) -> int 21 | def Fib(n: float) -> int 22 | def MultiOverload(a: int) -> int 23 | def MultiOverload(a: float) -> float 24 | def MultiOverload(a: str) -> str 25 | def MultiOverload(a : list) -> list 26 | def ExceptionOverload() -> None raises simple.WrongException 27 | def ExceptionOverload() -> None raises simple.BadException 28 | -------------------------------------------------------------------------------- /tests/simple.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | """Used for tests.""" 19 | 20 | # pylint: disable=unused-argument 21 | 22 | import sys 23 | from pytypedecl import checker 24 | 25 | 26 | # def IntToInt(i :int) -> int 27 | def IntToInt(i): 28 | return 42 29 | 30 | 31 | # def MultiArgs(a : int, b: int, c:str, d: str) -> None 32 | def MultiArgs(a, b, c, d): 33 | return None 34 | 35 | 36 | # def GoodRet() -> int 37 | def GoodRet(): 38 | return 42 39 | 40 | 41 | # def BadRet() -> int 42 | def BadRet(): 43 | return "I want integer" 44 | 45 | 46 | # def NoneRet() -> None 47 | def NoneRet(): 48 | return [1337] 49 | 50 | 51 | class Apple(object): 52 | pass 53 | 54 | 55 | class Banana(object): 56 | pass 57 | 58 | 59 | class Orange(object): 60 | pass 61 | 62 | 63 | # def AppleRet() -> Apple 64 | def AppleRet(): 65 | return Banana() # Intentionally returning the wrong type 66 | 67 | 68 | class FooException(Exception): 69 | pass 70 | 71 | 72 | class WrongException(Exception): 73 | pass 74 | 75 | 76 | class BadException(Exception): 77 | pass 78 | 79 | 80 | # def FooFail() -> None raise FooException 81 | def FooFail(): 82 | raise FooException 83 | 84 | 85 | # def WrongFail() -> None raise FooException, WrongException 86 | def WrongFail(): 87 | raise WrongException 88 | 89 | 90 | # def BadFail() -> None raise FooException, WrongException 91 | def BadFail(): 92 | raise BadException 93 | 94 | 95 | # def MultiFail(a: Apple) -> None raise FooException 96 | def MultiFail(a): 97 | raise BadException 98 | 99 | 100 | # def MultiArgsNoType(a: int, b, c, d: str, e) -> int 101 | def MultiArgsNoType(a, b, c, d, e): 102 | return a 103 | 104 | checker.CheckFromFile(sys.modules[__name__], __file__ + "td") 105 | -------------------------------------------------------------------------------- /tests/simple.pytd: -------------------------------------------------------------------------------- 1 | # -*- mode: python; coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | def IntToInt(i :int) -> int 19 | def MultiArgs(a : int, b: int, c:dict, d: str) -> None 20 | def GoodRet() -> int 21 | def BadRet() -> int 22 | def NoneRet() -> None 23 | def AppleRet() -> Apple 24 | def FooFail() -> None raises FooException 25 | def WrongFail() -> None raises FooException, WrongException 26 | def BadFail() -> None raises FooException, WrongException 27 | def MultiFail(a : Apple) -> None raises FooException 28 | def MultiArgsNoType(a: int, b, c, d: str, e) -> int 29 | -------------------------------------------------------------------------------- /tests/union.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | """Used for tests.""" 19 | 20 | # pylint: disable=unused-argument 21 | # pylint: disable=unused-import 22 | # pylint: disable=g-line-too-long 23 | 24 | import sys 25 | from pytypedecl import checker 26 | # some signatures use type defined in the simple module 27 | from tests import simple 28 | 29 | 30 | # def StrToInt(s : str or None) -> int 31 | def StrToInt(s): 32 | if s is None: 33 | return 0 34 | return int(s) 35 | 36 | 37 | # def Add(a: int or None, b: int or None) -> int or None 38 | # def Add(a: float or None, b: float or None) -> float or None 39 | def Add(a, b): 40 | if a is None or b is None: 41 | return None 42 | return a + b 43 | 44 | 45 | # def AddToFloat(a: int | float, b: int | float) -> float 46 | def IntOrFloat(a, b): 47 | return 42.0 48 | 49 | 50 | # def IntOrNone(a : int | None) -> int | None 51 | def IntOrNone(a): 52 | return a 53 | 54 | 55 | # def AppleOrBananaOrOrange( 56 | # f : simple.Apple | simple.Banana | simple.Orange) -> None 57 | def AppleOrBananaOrOrange(f): 58 | return None 59 | 60 | 61 | class Readable(object): 62 | 63 | def Read(self): 64 | pass 65 | 66 | 67 | class Writable(object): 68 | 69 | def Write(self): 70 | pass 71 | 72 | 73 | # def DoSomeIOStuff(f : Readable and Writable) -> str 74 | def DoSomeIOStuff(f): 75 | return "cool" 76 | 77 | 78 | # def UnionReturn() -> list | tuple 79 | def UnionReturn(): 80 | return [42] 81 | 82 | 83 | # def UnionReturnError() -> int | list 84 | def UnionReturnError(): 85 | return 42, 86 | 87 | 88 | class File(Readable, Writable): 89 | pass 90 | 91 | 92 | checker.CheckFromFile(sys.modules[__name__], __file__ + "td") 93 | -------------------------------------------------------------------------------- /tests/union.pytd: -------------------------------------------------------------------------------- 1 | # -*- mode: python; coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | def StrToInt(s : str or None) -> int 19 | def Add(a: int or None, b: int or None) -> int or None 20 | def Add(a: float or None, b: float or None) -> float or None 21 | def IntOrFloat(a: int or float, b: int or float) -> float 22 | def IntOrNone(a : int or None) -> int or None 23 | def DoSomeIOStuff(f : Readable and Writable) -> str 24 | def UnionReturn() -> list or tuple 25 | def UnionReturnError() -> int or list 26 | def AppleOrBananaOrOrange(f : simple.Apple or simple.Banana or simple.Orange) -> None 27 | -------------------------------------------------------------------------------- /type_match_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | # Copyright 2013 Google Inc. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | """Tests for type_match.py.""" 17 | 18 | import textwrap 19 | import unittest 20 | 21 | 22 | from pytypedecl import booleq 23 | from pytypedecl import pytd 24 | from pytypedecl import type_match 25 | from pytypedecl.parse import parser 26 | from pytypedecl.parse import visitors 27 | 28 | 29 | class TestTypeMatch(unittest.TestCase): 30 | """Test algorithms and datastructures of booleq.py.""" 31 | 32 | def testUnknown(self): 33 | m = type_match.TypeMatch({}) 34 | eq = m.match_type_against_type(pytd.AnythingType(), pytd.AnythingType(), {}) 35 | self.assertEquals(eq, booleq.TRUE) 36 | 37 | def testNothing(self): 38 | m = type_match.TypeMatch({}) 39 | eq = m.match_type_against_type(pytd.NothingType(), 40 | pytd.NamedType("A"), {}) 41 | self.assertEquals(eq, booleq.FALSE) 42 | 43 | def testNamed(self): 44 | m = type_match.TypeMatch({}) 45 | eq = m.match_type_against_type(pytd.NamedType("A"), pytd.NamedType("A"), {}) 46 | self.assertEquals(eq, booleq.TRUE) 47 | eq = m.match_type_against_type(pytd.NamedType("A"), pytd.NamedType("B"), {}) 48 | self.assertNotEquals(eq, booleq.TRUE) 49 | 50 | def testNamedAgainstGeneric(self): 51 | m = type_match.TypeMatch({}) 52 | eq = m.match_type_against_type(pytd.GenericType(pytd.NamedType("A"), []), 53 | pytd.NamedType("A"), {}) 54 | self.assertEquals(eq, booleq.TRUE) 55 | 56 | def testFunction(self): 57 | ast = parser.parse_string(textwrap.dedent(""" 58 | def left(a: int) -> int 59 | def right(a: int) -> int 60 | """)) 61 | m = type_match.TypeMatch() 62 | self.assertEquals(m.match(ast.Lookup("left"), ast.Lookup("right"), {}), 63 | booleq.TRUE) 64 | 65 | def testReturn(self): 66 | ast = parser.parse_string(textwrap.dedent(""" 67 | def left(a: int) -> float 68 | def right(a: int) -> int 69 | """)) 70 | m = type_match.TypeMatch() 71 | self.assertNotEquals(m.match(ast.Lookup("left"), ast.Lookup("right"), {}), 72 | booleq.TRUE) 73 | 74 | def testOptional(self): 75 | ast = parser.parse_string(textwrap.dedent(""" 76 | def left(a: int) -> int 77 | def right(a: int, ...) -> int 78 | """)) 79 | m = type_match.TypeMatch() 80 | self.assertEquals(m.match(ast.Lookup("left"), ast.Lookup("right"), {}), 81 | booleq.TRUE) 82 | 83 | def testGeneric(self): 84 | ast = parser.parse_string(textwrap.dedent(""" 85 | class A(nothing): 86 | pass 87 | left: A 88 | right: A 89 | """)) 90 | ast = visitors.LookupClasses(ast) 91 | m = type_match.TypeMatch() 92 | self.assertEquals(m.match_type_against_type( 93 | ast.Lookup("left").type, 94 | ast.Lookup("right").type, {}), booleq.TRUE) 95 | 96 | def testClassMatch(self): 97 | ast = parser.parse_string(textwrap.dedent(""" 98 | class Left(nothing): 99 | def method(self) -> ? 100 | class Right(nothing): 101 | def method(self) -> ? 102 | def method2(self) -> ? 103 | """)) 104 | ast = visitors.LookupClasses(ast) 105 | m = type_match.TypeMatch() 106 | left, right = ast.Lookup("Left"), ast.Lookup("Right") 107 | self.assertEquals(m.match(left, right, {}), booleq.TRUE) 108 | self.assertNotEquals(m.match(right, left, {}), booleq.TRUE) 109 | 110 | def testSubclasses(self): 111 | ast = parser.parse_string(textwrap.dedent(""" 112 | class A(nothing): 113 | pass 114 | class B(A): 115 | pass 116 | a : A 117 | def left(a: B) -> B 118 | def right(a: A) -> A 119 | """)) 120 | ast = visitors.LookupClasses(ast) 121 | m = type_match.TypeMatch({ast.Lookup("a").type: [ast.Lookup("B")]}) 122 | left, right = ast.Lookup("left"), ast.Lookup("right") 123 | self.assertEquals(m.match(left, right, {}), booleq.TRUE) 124 | self.assertNotEquals(m.match(right, left, {}), booleq.TRUE) 125 | 126 | 127 | if __name__ == "__main__": 128 | unittest.main() 129 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | # Copyright 2013 Google Inc. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | 17 | """Utilities for pytypedecl. 18 | 19 | This provides a utility function to access data files in a way that works either 20 | locally or within a larger repository. 21 | """ 22 | 23 | import collections 24 | import os 25 | 26 | 27 | from pytypedecl import pytd 28 | from pytypedecl.parse import visitors 29 | 30 | 31 | def GetDataFile(filename=""): 32 | full_filename = os.path.abspath( 33 | os.path.join(os.path.dirname(pytd.__file__), filename)) 34 | with open(full_filename, "rb") as fi: 35 | return fi.read() 36 | 37 | 38 | def UnpackUnion(t): 39 | """Return the type list for union type, or a list with the type itself.""" 40 | if isinstance(t, pytd.UnionType): 41 | return t.type_list 42 | else: 43 | return [t] 44 | 45 | 46 | def Concat(pytd1, pytd2): 47 | """Concatenate two pytd ASTs.""" 48 | assert isinstance(pytd1, pytd.TypeDeclUnit) 49 | assert isinstance(pytd2, pytd.TypeDeclUnit) 50 | return pytd.TypeDeclUnit(name=pytd1.name + " + " + pytd2.name, 51 | constants=pytd1.constants + pytd2.constants, 52 | classes=pytd1.classes + pytd2.classes, 53 | functions=pytd1.functions + pytd2.functions, 54 | modules=pytd1.modules + pytd2.modules) 55 | 56 | 57 | def JoinTypes(types): 58 | """Combine a list of types into a union type, if needed. 59 | 60 | Leaves singular return values alone, or wraps a UnionType around them if there 61 | are multiple ones, or if there are no elements in the list (or only 62 | NothingType) return NothingType. 63 | 64 | Arguments: 65 | types: A list of types. This list might contain other UnionTypes. If 66 | so, they are flattened. 67 | 68 | Returns: 69 | A type that represents the union of the types passed in. Order is preserved. 70 | """ 71 | queue = collections.deque(types) 72 | seen = set() 73 | new_types = [] 74 | while queue: 75 | t = queue.popleft() 76 | if isinstance(t, pytd.UnionType): 77 | queue.extendleft(reversed(t.type_list)) 78 | elif isinstance(t, pytd.NothingType): 79 | pass 80 | elif t not in seen: 81 | new_types.append(t) 82 | seen.add(t) 83 | 84 | if len(new_types) == 1: 85 | return new_types.pop() 86 | elif any(isinstance(t, pytd.AnythingType) for t in new_types): 87 | return pytd.AnythingType() 88 | elif new_types: 89 | return pytd.UnionType(tuple(new_types)) # tuple() to make unions hashable 90 | else: 91 | return pytd.NothingType() 92 | 93 | 94 | # pylint: disable=invalid-name 95 | def prevent_direct_instantiation(cls, *args, **kwargs): 96 | """Mix-in method for creating abstract (base) classes. 97 | 98 | Use it like this to prevent instantiation of classes: 99 | 100 | class Foo(object): 101 | __new__ = prevent_direct_instantiation 102 | 103 | This will apply to the class itself, not its subclasses, so it can be used to 104 | create base classes that are abstract, but will become concrete once inherited 105 | from. 106 | 107 | Arguments: 108 | cls: The class to instantiate, passed to __new__. 109 | *args: Additional arguments, passed to __new__. 110 | **kwargs: Additional keyword arguments, passed to __new__. 111 | Returns: 112 | A new instance. 113 | Raises: 114 | AssertionError: If something tried to instantiate the base class. 115 | """ 116 | new = cls.__dict__.get("__new__") 117 | if getattr(new, "__func__", None) == prevent_direct_instantiation: 118 | raise AssertionError("Can't instantiate %s directly" % cls.__name__) 119 | return object.__new__(cls, *args, **kwargs) 120 | 121 | 122 | class TypeMatcher(object): 123 | """Base class for modules that match types against each other. 124 | 125 | Maps pytd node types (, ) to a method "match__". 126 | So e.g. to write a matcher that compares Functions by name, you would write: 127 | 128 | class MyMatcher(TypeMatcher): 129 | 130 | def match_function_function(self, f1, f2): 131 | return f1.name == f2.name 132 | """ 133 | 134 | def default_match(self, t1, t2): 135 | return t1 == t2 136 | 137 | def match(self, t1, t2, *args, **kwargs): 138 | name1 = t1.__class__.__name__ 139 | name2 = t2.__class__.__name__ 140 | f = getattr(self, "match_" + name1.lower() + "_against_" + name2.lower(), 141 | None) 142 | if f: 143 | return f(t1, t2, *args, **kwargs) 144 | else: 145 | return self.default_match(t1, t2, *args, **kwargs) 146 | 147 | 148 | 149 | def CanonicalOrdering(n): 150 | """Convert a PYTD node to a canonical (sorted) ordering.""" 151 | # TODO: use the original .py to decide the ordering rather 152 | # than an arbitrary sort order 153 | return n.Visit(visitors.CanonicalOrderingVisitor()) 154 | -------------------------------------------------------------------------------- /utils_test.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*- 2 | # Copyright 2013 Google Inc. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | 17 | import textwrap 18 | import unittest 19 | from pytypedecl import pytd 20 | from pytypedecl import utils 21 | from pytypedecl.parse import parser_test 22 | 23 | 24 | class TestUtils(parser_test.ParserTest): 25 | """Test the visitors in optimize.py.""" 26 | 27 | def testGetDataFileReturnsString(self): 28 | # smoke test, only checks that it doesn't throw and the result is a string 29 | self.assertIsInstance(utils.GetDataFile("builtins/errno.pytd"), str) 30 | 31 | def testUnpackUnion(self): 32 | """Test for UnpackUnion.""" 33 | ast = self.Parse(""" 34 | c1: int or float 35 | c2: int 36 | c3: list""") 37 | c1 = ast.Lookup("c1").type 38 | c2 = ast.Lookup("c2").type 39 | c3 = ast.Lookup("c3").type 40 | self.assertItemsEqual(utils.UnpackUnion(c1), c1.type_list) 41 | self.assertItemsEqual(utils.UnpackUnion(c2), [c2]) 42 | self.assertItemsEqual(utils.UnpackUnion(c3), [c3]) 43 | 44 | def testConcat(self): 45 | """Test for concatenating two pytd ASTs.""" 46 | ast1 = self.Parse(""" 47 | c1: int 48 | 49 | def f1() -> int 50 | 51 | class Class1: 52 | pass 53 | """) 54 | ast2 = self.Parse(""" 55 | c2: int 56 | 57 | def f2() -> int 58 | 59 | class Class2: 60 | pass 61 | """) 62 | expected = textwrap.dedent(""" 63 | c1: int 64 | c2: int 65 | 66 | def f1() -> int 67 | def f2() -> int 68 | 69 | class Class1: 70 | pass 71 | 72 | class Class2: 73 | pass 74 | """) 75 | combined = utils.Concat(ast1, ast2) 76 | self.AssertSourceEquals(combined, expected) 77 | 78 | def testJoinTypes(self): 79 | """Test that JoinTypes() does recursive flattening.""" 80 | n1, n2, n3, n4, n5, n6 = [pytd.NamedType("n%d" % i) for i in xrange(6)] 81 | # n1 or (n2 or (n3)) 82 | nested1 = pytd.UnionType((n1, pytd.UnionType((n2, pytd.UnionType((n3,)))))) 83 | # ((n4) or n5) or n6 84 | nested2 = pytd.UnionType((pytd.UnionType((pytd.UnionType((n4,)), n5)), n6)) 85 | joined = utils.JoinTypes([nested1, nested2]) 86 | self.assertEquals(joined.type_list, 87 | (n1, n2, n3, n4, n5, n6)) 88 | 89 | def testJoinSingleType(self): 90 | """Test that JoinTypes() returns single types as-is.""" 91 | a = pytd.NamedType("a") 92 | self.assertEquals(utils.JoinTypes([a]), a) 93 | self.assertEquals(utils.JoinTypes([a, a]), a) 94 | 95 | def testJoinNothingType(self): 96 | """Test that JoinTypes() removes or collapses 'nothing'.""" 97 | a = pytd.NamedType("a") 98 | nothing = pytd.NothingType() 99 | self.assertEquals(utils.JoinTypes([a, nothing]), a) 100 | self.assertEquals(utils.JoinTypes([nothing]), nothing) 101 | self.assertEquals(utils.JoinTypes([nothing, nothing]), nothing) 102 | 103 | def testJoinEmptyTypesToNothing(self): 104 | """Test that JoinTypes() simplifies empty unions to 'nothing'.""" 105 | self.assertIsInstance(utils.JoinTypes([]), pytd.NothingType) 106 | 107 | def testJoinAnythingTypes(self): 108 | """Test that JoinTypes() simplifies unions containing '?'.""" 109 | types = [pytd.AnythingType(), pytd.NamedType("a")] 110 | self.assertIsInstance(utils.JoinTypes(types), pytd.AnythingType) 111 | 112 | def testTypeMatcher(self): 113 | """Test for the TypeMatcher class.""" 114 | 115 | class MyTypeMatcher(utils.TypeMatcher): 116 | 117 | def default_match(self, t1, t2, mykeyword): 118 | assert mykeyword == "foobar" 119 | return t1 == t2 120 | 121 | def match_function_against_function(self, f1, f2, mykeyword): 122 | assert mykeyword == "foobar" 123 | return all(self.match(sig1, sig2, mykeyword) 124 | for sig1, sig2 in zip(f1.signatures, f2.signatures)) 125 | 126 | s1 = pytd.Signature((), pytd.NothingType(), (), (), False) 127 | s2 = pytd.Signature((), pytd.AnythingType(), (), (), False) 128 | match1 = MyTypeMatcher().match(pytd.Function("f1", (s1, s2)), 129 | pytd.Function("f2", (s1, s2)), 130 | mykeyword="foobar") 131 | self.assertEquals(match1, True) 132 | match2 = MyTypeMatcher().match(pytd.Function("f1", (s1, s2)), 133 | pytd.Function("f2", (s2, s2)), 134 | mykeyword="foobar") 135 | self.assertEquals(match2, False) 136 | 137 | 138 | if __name__ == "__main__": 139 | unittest.main() 140 | --------------------------------------------------------------------------------