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
|
"""
This file defines the 'subset' SomeValue classes.
An instance of a SomeValue class stands for a Python object that has some
known properties, for example that is known to be a list of non-negative
integers. Each instance can be considered as an object that is only
'partially defined'. Another point of view is that each instance is a
generic element in some specific subset of the set of all objects.
"""
# Old terminology still in use here and there:
# SomeValue means one of the SomeXxx classes in this file.
# Cell is an instance of one of these classes.
#
# Think about cells as potato-shaped circles in a diagram:
# ______________________________________________________
# / SomeObject() \
# / ___________________________ ______________ \
# | / SomeInteger(nonneg=False) \____ / SomeString() \ \
# | / __________________________ \ | | |
# | | / SomeInteger(nonneg=True) \ | | "hello" | |
# | | | 0 42 _________/ | \______________/ |
# | \ -3 \________________/ / |
# \ \ -5 _____/ /
# \ \________________________/ 3.1416 /
# \_____________________________________________________/
#
from types import ClassType, BuiltinFunctionType, FunctionType, MethodType
from types import InstanceType
import pypy
from pypy.annotation.pairtype import pair, extendabletype
from pypy.objspace.flow.model import Constant
from pypy.tool.cache import Cache
import inspect
DEBUG = True # set to False to disable recording of debugging information
class SomeObject:
"""The set of all objects. Each instance stands
for an arbitrary object about which nothing is known."""
__metaclass__ = extendabletype
knowntype = object
def __eq__(self, other):
return (self.__class__ is other.__class__ and
self.__dict__ == other.__dict__)
def __ne__(self, other):
return not (self == other)
def __repr__(self):
items = self.__dict__.items()
items.sort()
args = []
for k, v in items:
m = getattr(self, 'fmt_' + k, repr)
r = m(v)
if r is not None:
args.append('%s=%s'%(k, r))
kwds = ', '.join(args)
return '%s(%s)' % (self.__class__.__name__, kwds)
def fmt_knowntype(self, t):
return t.__name__
def contains(self, other):
return self == other or pair(self, other).union() == self
def is_constant(self):
return hasattr(self, 'const')
# for debugging, record where each instance comes from
# this is disabled if DEBUG is set to False
_coming_from = {}
def __new__(cls, *args, **kw):
self = super(SomeObject, cls).__new__(cls, *args, **kw)
if DEBUG:
try:
bookkeeper = pypy.annotation.bookkeeper.getbookkeeper()
position_key = bookkeeper.position_key
except AttributeError:
pass
else:
SomeObject._coming_from[id(self)] = position_key, None
return self
def origin(self):
return SomeObject._coming_from.get(id(self), (None, None))[0]
origin = property(origin)
def caused_by_merge(self):
return SomeObject._coming_from.get(id(self), (None, None))[1]
def set_caused_by_merge(self, nvalue):
SomeObject._coming_from[id(self)] = self.origin, nvalue
caused_by_merge = property(caused_by_merge, set_caused_by_merge)
del set_caused_by_merge
class SomeInteger(SomeObject):
"Stands for an object which is known to be an integer."
knowntype = int
def __init__(self, nonneg=False, unsigned=False):
self.nonneg = nonneg
self.unsigned = unsigned # pypy.objspace.std.restricted_int.r_uint
class SomeBool(SomeInteger):
"Stands for true or false."
knowntype = bool
nonneg = True
unsigned = False
def __init__(self):
pass
class SomeString(SomeObject):
"Stands for an object which is known to be a string."
knowntype = str
class SomeChar(SomeString):
"Stands for an object known to be a string of length 1."
class SomeList(SomeObject):
"Stands for a homogenous list of any length."
knowntype = list
def __init__(self, factories, s_item=SomeObject()):
self.factories = factories
self.s_item = s_item # general enough for any element
class SomeTuple(SomeObject):
"Stands for a tuple of known length."
knowntype = tuple
def __init__(self, items):
self.items = tuple(items) # tuple of s_xxx elements
for i in items:
if not i.is_constant():
break
else:
self.const = tuple([i.const for i in items])
class SomeDict(SomeObject):
"Stands for a dict."
knowntype = dict
def __init__(self, factories, s_key, s_value):
self.factories = factories
self.s_key = s_key
self.s_value = s_value
class SomeIterator(SomeObject):
"Stands for an iterator returning objects of a known type."
knowntype = type(iter([])) # arbitrarily chose seqiter as the type
def __init__(self, s_item=SomeObject()):
self.s_item = s_item
class SomeInstance(SomeObject):
"Stands for an instance of a (user-defined) class."
def __init__(self, classdef):
self.classdef = classdef
self.knowntype = classdef.cls
self.revision = classdef.revision
def fmt_knowntype(self, kt):
return None
def fmt_classdef(self, cd):
return cd.cls.__name__
def new_or_old_class(c):
if hasattr(c, '__class__'):
return c.__class__
else:
return type(c)
class SomePBC(SomeObject):
"""Stands for a global user instance, built prior to the analysis,
or a set of such instances."""
def __init__(self, prebuiltinstances):
# prebuiltinstances is a dictionary containing concrete python
# objects as keys.
# if the key is a function, the value can be a classdef to
# indicate that it is really a method.
prebuiltinstances = prebuiltinstances.copy()
self.prebuiltinstances = prebuiltinstances
self.simplify()
self.knowntype = reduce(commonbase,
[new_or_old_class(x) for x in prebuiltinstances])
if prebuiltinstances.values() == [True]:
# hack for the convenience of direct callers to SomePBC():
# only if there is a single object in prebuiltinstances and
# it doesn't have an associated ClassDef
self.const, = prebuiltinstances
def simplify(self):
# We check that the dictionary does not contain at the same time
# a function bound to a classdef, and constant bound method objects
# on that class.
for x, ignored in self.prebuiltinstances.items():
if isinstance(x, MethodType) and x.im_func in self.prebuiltinstances:
classdef = self.prebuiltinstances[x.im_func]
if isinstance(x.im_self, classdef.cls):
del self.prebuiltinstances[x]
def fmt_prebuiltinstances(self, pbis):
if hasattr(self, 'const'):
return None
else:
return '{...%s...}'%(len(pbis),)
def fmt_knowntype(self, kt):
if self.is_constant():
return None
else:
return kt.__name__
class SomeBuiltin(SomeObject):
"Stands for a built-in function or method with special-cased analysis."
knowntype = BuiltinFunctionType # == BuiltinMethodType
def __init__(self, analyser, s_self=None):
self.analyser = analyser
self.s_self = s_self
class SomeImpossibleValue(SomeObject):
"""The empty set. Instances are placeholders for objects that
will never show up at run-time, e.g. elements of an empty list."""
def unionof(*somevalues):
"The most precise SomeValue instance that contains all the values."
s1 = SomeImpossibleValue()
for s2 in somevalues:
if s1 != s2:
s1 = pair(s1, s2).union()
if DEBUG and s1.caused_by_merge is None and len(somevalues) > 1:
s1.caused_by_merge = somevalues
return s1
# ____________________________________________________________
# internal
def setunion(d1, d2):
"Union of two sets represented as dictionaries."
d = d1.copy()
d.update(d2)
return d
def set(it):
"Turn an iterable into a set."
d = {}
for x in it:
d[x] = True
return d
def commonbase(cls1, cls2): # XXX single inheritance only XXX hum
l1 = inspect.getmro(cls1)
l2 = inspect.getmro(cls2)
if l1[-1] != object:
l1 = l1 + (object,)
if l2[-1] != object:
l2 = l2 + (object,)
for x in l1:
if x in l2:
return x
assert 0, "couldn't get to commonbase of %r and %r" % (cls1, cls2)
def missing_operation(cls, name):
def default_op(*args):
#print '* warning, no type available for %s(%s)' % (
# name, ', '.join([repr(a) for a in args]))
return SomeObject()
setattr(cls, name, default_op)
# this has the side-effect of registering the unary and binary operations
from pypy.annotation.unaryop import UNARY_OPERATIONS
from pypy.annotation.binaryop import BINARY_OPERATIONS
|