This repository was archived by the owner on May 11, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathpytd.py
More file actions
executable file
·392 lines (293 loc) · 12 KB
/
pytd.py
File metadata and controls
executable file
·392 lines (293 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# -*- coding:utf-8; python-indent:2; indent-tabs-mode:nil -*-
# Copyright 2013 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Our way of using namedtuple is confusing pylint.
# pylint: disable=no-member
"""AST representation of a pytd file."""
import itertools
import re
from pytypedecl.parse import node
# TODO(ampere): Add __new__ to Type subclasses that contain sequences to
# convert arguments to tuples?
class TypeDeclUnit(node.Node('name', 'constants', 'classes', 'functions',
'modules')):
"""Module node. Holds module contents (classes / functions) and submodules.
Attributes:
name: Name of this module, or None for the top-level module.
constants: Iterable of module-level constants.
functions: Iterable of functions defined in this type decl unit.
classes: Iterable of classes defined in this type decl unit.
modules: Iterable of submodules of the current module.
"""
__slots__ = ()
def Lookup(self, name):
"""Convenience function: Look up a given name in the global namespace.
Tries to find a constant, function or class by this name.
Args:
name: Name to look up.
Returns:
A Constant, Function or Class.
Raises:
KeyError: if this identifier doesn't exist.
"""
# TODO: Remove. Change constants, classes and functions to dict.
try:
return self._name2item[name]
except AttributeError:
self._name2item = {}
for x in self.constants + self.functions + self.classes + self.modules:
self._name2item[x.name] = x
return self._name2item[name]
def __hash__(self):
return id(self)
def __eq__(self, other):
return id(self) == id(other)
def __ne__(self, other):
return id(self) != id(other)
class Constant(node.Node('name', 'type')):
__slots__ = ()
class Class(node.Node('name', 'parents', 'methods', 'constants', 'template')):
"""Represents a class declaration.
Used as dict/set key, so all components must be hashable.
Attributes:
name: Class name (string)
parents: The super classes of this class (instances of Type).
methods: Tuple of class methods (instances of Function).
constants: Tuple of constant class attributes (instances of Constant).
template: Tuple of TemplateItem instances.
"""
# TODO: Rename "parents" to "bases". "Parents" is confusing since we're
# in a tree.
__slots__ = ()
def Lookup(self, name):
"""Convenience function: Look up a given name in the class namespace.
Tries to find a method or constant by this name in the class.
Args:
name: Name to look up.
Returns:
A Constant or Function instance.
Raises:
KeyError: if this identifier doesn't exist in this class.
"""
# TODO: Remove this. Make methods and constants dictionaries.
try:
return self._name2item[name]
except AttributeError:
self._name2item = {}
for x in self.methods + self.constants:
self._name2item[x.name] = x
return self._name2item[name]
class Function(node.Node('name', 'signatures')):
"""A function or a method.
Attributes:
name: The name of this function.
signatures: Possible list of parameter type combinations for this function.
"""
__slots__ = ()
class Signature(node.Node('params', 'return_type', 'exceptions', 'template',
'has_optional')):
"""Represents an individual signature of a function.
For overloaded functions, this is one specific combination of parameters.
For non-overloaded functions, there is a 1:1 correspondence between function
and signature.
Attributes:
name: The name of this function.
params: The list of parameters for this function definition.
return_type: The return type of this function.
exceptions: List of exceptions for this function definition.
template: names for bindings for bounded types in params/return_type
has_optional: Do we have optional parameters ("...")?
"""
__slots__ = ()
class Parameter(node.Node('name', 'type')):
"""Represents a parameter of a function definition.
Attributes:
name: The name of the parameter.
type: The type of the parameter.
"""
__slots__ = ()
# Conceptually, this is a subtype of Parameter:
class MutableParameter(node.Node('name', 'type', 'new_type')):
"""Represents a parameter that's modified by the function.
Attributes:
name: The name of the parameter.
type: The type of the parameter.
new_type: The type the parameter will have after the function is called.
"""
__slots__ = ()
class TypeParameter(node.Node('name')):
"""Represents a type parameter.
A type parameter is a bound variable in the context of a function or class
definition. It specifies an equivalence between types.
For example, this defines a identity function:
def f<T>(x: T) -> T
"""
__slots__ = ()
class TemplateItem(node.Node('type_param', 'within_type')):
"""Represents "template name extends bounded_type".
This is used for classes and signatures. The 'template' field of both is
a list of TemplateItems. Note that *using* the template happens through
TypeParameters. E.g. in:
class A<T>:
def f(T x) -> T
both the "T"s in the definition of f() are using pytd.TypeParameter to refer
to the TemplateItem in class A's template.
Attributes:
type_param: the TypeParameter instance used. This is the actual instance
that's used wherever this type parameter appears, e.g. within a class.
within_type: the "extends" type for this name (e.g., NamedType('object'))
"""
__slots__ = ()
@property
def name(self):
return self.type_param.name
# Types can be:
# 1.) NamedType:
# Specifies a type by name (i.e., a string)
# 2.) NativeType
# Points to a Python type. (int, float etc.)
# 3.) ClassType
# Points back to a Class in the AST. (This makes the AST circular)
# 4.) GenericType
# Contains a base type and parameters.
# 5.) UnionType / IntersectionType
# Can be multiple types at once.
# 6.) NothingType / AnythingType
# Special purpose types that represent nothing or everything.
# 7.) TypeParameter
# A placeholder for a type.
# 8.) Scalar
# A singleton type. Not currently used, but supported by the parser.
# For 1-3, the file visitors.py contains tools for converting between the
# corresponding AST representations.
class NamedType(node.Node('name')):
"""A type specified by name."""
__slots__ = ()
def __str__(self):
return str(self.name)
class NativeType(node.Node('python_type')):
"""A type specified by a native Python type. Used during runtime checking."""
__slots__ = ()
class ClassType(node.Node('name')):
"""A type specified through an existing class node."""
# This type is different from normal nodes:
# (a) It's mutable, and there are functions (parse/visitors.py:FillInClasses)
# that modify a tree in place.
# (b) Because it's mutable, it's not actually using the tuple/Node interface
# to store things (in particular, the pointer to the existing class).
# (c) Visitors will not process the "children" of this node. Since we point
# to classes that are back at the top of the tree, that would generate
# cycles.
__slots__ = ()
def __new__(cls, name, clsref=None):
self = super(ClassType, cls).__new__(cls, name)
self.cls = clsref # potentially filled in later (by visitors.FillInClasses)
return self
# __eq__ is inherited (using tuple equality + requiring the two classes
# be the same)
def __str__(self):
return str(self.cls.name) if self.cls else self.name
def __repr__(self):
return '{type}{cls}({name})'.format(
type=type(self).__name__, name=self.name,
cls='<unresolved>' if self.cls is None else '')
class AnythingType(node.Node()):
"""A type we know nothing about yet ('?' in pytd)."""
__slots__ = ()
class NothingType(node.Node()):
"""An "impossible" type, with no instances ('nothing' in pytd).
Also known as the "uninhabited" type. For representing empty lists, and
functions that never return.
"""
__slots__ = ()
class Scalar(node.Node('value')):
__slots__ = ()
class UnionType(node.Node('type_list')):
"""A union type that contains all types in self.type_list."""
__slots__ = ()
# NOTE: type_list is kept as a tuple, to preserve the original order
# even though in most respects it acts like a frozenset.
# It also flattens the input, such that printing without
# parentheses gives the same result.
def __new__(cls, type_list):
assert type_list # Disallow empty unions. Use NothingType for these.
flattened = itertools.chain.from_iterable(
t.type_list if isinstance(t, UnionType) else [t] for t in type_list)
return super(UnionType, cls).__new__(cls, tuple(flattened))
def __hash__(self):
# See __eq__ - order doesn't matter, so use frozenset
return hash(frozenset(self.type_list))
def __eq__(self, other):
if self is other:
return True
if isinstance(other, UnionType):
# equality doesn't care about the ordering of the type_list
return frozenset(self.type_list) == frozenset(other.type_list)
return NotImplemented
def __ne__(self, other):
return not self == other
# TODO: Do we still need this?
class IntersectionType(node.Node('type_list')):
"""An intersection type that contains all types in self.type_list."""
__slots__ = ()
# NOTE: type_list is kept as a tuple, to preserve the original order
# even though in most respects it acts like a frozenset.
# It also flattens the input, such that printing without
# parentheses gives the same result.
def __new__(cls, type_list):
flattened = itertools.chain.from_iterable(
t.type_list if isinstance(t, IntersectionType) else [t]
for t in type_list)
return super(IntersectionType, cls).__new__(cls, tuple(flattened))
def __hash__(self):
# See __eq__ - order doesn't matter, so use frozenset
return hash(frozenset(self.type_list))
def __eq__(self, other):
if self is other:
return True
if isinstance(other, IntersectionType):
# equality doesn't care about the ordering of the type_list
return frozenset(self.type_list) == frozenset(other.type_list)
return NotImplemented
def __ne__(self, other):
return not self == other
class GenericType(node.Node('base_type', 'parameters')):
"""Generic type. Takes a base type and type paramters.
This corresponds to the syntax: type<type1,>, type<type1, type2> (etc.).
Attributes:
base_type: The base type. Instance of Type.
parameters: Type paramters. Tuple of instances of Type.
"""
__slots__ = ()
class HomogeneousContainerType(GenericType):
"""Special generic type for homogeneous containers. Only has one type param.
This differs from GenericType in that it assumes *all* items in a container
will be the same type. The syntax is type<t>. (Vs type<t,> for GenericType.)
"""
__slots__ = ()
@property
def element_type(self):
return self.parameters[0]
# So we can do "isinstance(node, pytd.TYPE)":
TYPE = (NamedType, NativeType, ClassType, AnythingType, UnionType,
NothingType, GenericType, TypeParameter, Scalar,
IntersectionType, Scalar)
def Print(n):
"""Convert a PYTD node to a string."""
# TODO: fix circular import
from pytypedecl.parse import visitors
res = n.Visit(visitors.PrintVisitor())
# Remove trailing blanks on lines (*not* \s which includes \n) -- these come
# from indents that have no other code on them.
return re.sub(r" +\n", "\n", res)