Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(80)

Side by Side Diff: third_party/logilab/astng/inference.py

Issue 719313003: Revert "pylint: upgrade to 1.3.1" (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « third_party/logilab/astng/exceptions.py ('k') | third_party/logilab/astng/inspector.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr 2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
3 # copyright 2003-2010 Sylvain Thenault, all rights reserved.
4 # contact mailto:thenault@gmail.com
3 # 5 #
4 # This file is part of astroid. 6 # This file is part of logilab-astng.
5 # 7 #
6 # astroid is free software: you can redistribute it and/or modify it 8 # logilab-astng is free software: you can redistribute it and/or modify it
7 # under the terms of the GNU Lesser General Public License as published by the 9 # under the terms of the GNU Lesser General Public License as published by the
8 # Free Software Foundation, either version 2.1 of the License, or (at your 10 # Free Software Foundation, either version 2.1 of the License, or (at your
9 # option) any later version. 11 # option) any later version.
10 # 12 #
11 # astroid is distributed in the hope that it will be useful, but 13 # logilab-astng is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 15 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
14 # for more details. 16 # for more details.
15 # 17 #
16 # You should have received a copy of the GNU Lesser General Public License along 18 # You should have received a copy of the GNU Lesser General Public License along
17 # with astroid. If not, see <http://www.gnu.org/licenses/>. 19 # with logilab-astng. If not, see <http://www.gnu.org/licenses/>.
18 """this module contains a set of functions to handle inference on astroid trees 20 """this module contains a set of functions to handle inference on astng trees
19 """ 21 """
20 22
21 __doctype__ = "restructuredtext en" 23 __doctype__ = "restructuredtext en"
22 24
23 from itertools import chain 25 from itertools import chain
26 import sys
24 27
25 from astroid import nodes 28 from logilab.astng import nodes
26 29
27 from astroid.manager import AstroidManager 30 from logilab.astng.manager import ASTNGManager
28 from astroid.exceptions import (AstroidError, InferenceError, NoDefault, 31 from logilab.astng.exceptions import (ASTNGBuildingException, ASTNGError,
29 NotFoundError, UnresolvableName) 32 InferenceError, NoDefault, NotFoundError, UnresolvableName)
30 from astroid.bases import (YES, Instance, InferenceContext, 33 from logilab.astng.bases import YES, Instance, InferenceContext, Generator, \
31 _infer_stmts, copy_context, path_wrapper, 34 _infer_stmts, copy_context, path_wrapper, raise_if_nothing_infered
32 raise_if_nothing_infered) 35 from logilab.astng.protocols import _arguments_infer_argname
33 from astroid.protocols import (
34 _arguments_infer_argname,
35 BIN_OP_METHOD, UNARY_OP_METHOD)
36 36
37 MANAGER = AstroidManager() 37 MANAGER = ASTNGManager()
38 38
39 39
40 class CallContext(object): 40 class CallContext:
41 """when inferring a function call, this class is used to remember values 41 """when inferring a function call, this class is used to remember values
42 given as argument 42 given as argument
43 """ 43 """
44 def __init__(self, args, starargs, dstarargs): 44 def __init__(self, args, starargs, dstarargs):
45 self.args = [] 45 self.args = []
46 self.nargs = {} 46 self.nargs = {}
47 for arg in args: 47 for arg in args:
48 if isinstance(arg, nodes.Keyword): 48 if isinstance(arg, nodes.Keyword):
49 self.nargs[arg.arg] = arg.value 49 self.nargs[arg.arg] = arg.value
50 else: 50 else:
51 self.args.append(arg) 51 self.args.append(arg)
52 self.starargs = starargs 52 self.starargs = starargs
53 self.dstarargs = dstarargs 53 self.dstarargs = dstarargs
54 54
55 def infer_argument(self, funcnode, name, context): 55 def infer_argument(self, funcnode, name, context):
56 """infer a function argument value according to the call context""" 56 """infer a function argument value according to the call context"""
57 # 1. search in named keywords 57 # 1. search in named keywords
58 try: 58 try:
59 return self.nargs[name].infer(context) 59 return self.nargs[name].infer(context)
60 except KeyError: 60 except KeyError:
61 # Function.args.args can be None in astroid (means that we don't hav e 61 # Function.args.args can be None in astng (means that we don't have
62 # information on argnames) 62 # information on argnames)
63 argindex = funcnode.args.find_argname(name)[0] 63 argindex = funcnode.args.find_argname(name)[0]
64 if argindex is not None: 64 if argindex is not None:
65 # 2. first argument of instance/class method 65 # 2. first argument of instance/class method
66 if argindex == 0 and funcnode.type in ('method', 'classmethod'): 66 if argindex == 0 and funcnode.type in ('method', 'classmethod'):
67 if context.boundnode is not None: 67 if context.boundnode is not None:
68 boundnode = context.boundnode 68 boundnode = context.boundnode
69 else: 69 else:
70 # XXX can do better ? 70 # XXX can do better ?
71 boundnode = funcnode.parent.frame() 71 boundnode = funcnode.parent.frame()
72 if funcnode.type == 'method': 72 if funcnode.type == 'method':
73 if not isinstance(boundnode, Instance): 73 if not isinstance(boundnode, Instance):
74 boundnode = Instance(boundnode) 74 boundnode = Instance(boundnode)
75 return iter((boundnode,)) 75 return iter((boundnode,))
76 if funcnode.type == 'classmethod': 76 if funcnode.type == 'classmethod':
77 return iter((boundnode,)) 77 return iter((boundnode,))
78 # if we have a method, extract one position
79 # from the index, so we'll take in account
80 # the extra parameter represented by `self` or `cls`
81 if funcnode.type in ('method', 'classmethod'):
82 argindex -= 1
83 # 2. search arg index 78 # 2. search arg index
84 try: 79 try:
85 return self.args[argindex].infer(context) 80 return self.args[argindex].infer(context)
86 except IndexError: 81 except IndexError:
87 pass 82 pass
88 # 3. search in *args (.starargs) 83 # 3. search in *args (.starargs)
89 if self.starargs is not None: 84 if self.starargs is not None:
90 its = [] 85 its = []
91 for infered in self.starargs.infer(context): 86 for infered in self.starargs.infer(context):
92 if infered is YES: 87 if infered is YES:
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
127 raise InferenceError(name) 122 raise InferenceError(name)
128 123
129 124
130 # .infer method ############################################################### 125 # .infer method ###############################################################
131 126
132 127
133 def infer_end(self, context=None): 128 def infer_end(self, context=None):
134 """inference's end for node such as Module, Class, Function, Const... 129 """inference's end for node such as Module, Class, Function, Const...
135 """ 130 """
136 yield self 131 yield self
137 nodes.Module._infer = infer_end 132 nodes.Module.infer = infer_end
138 nodes.Class._infer = infer_end 133 nodes.Class.infer = infer_end
139 nodes.Function._infer = infer_end 134 nodes.Function.infer = infer_end
140 nodes.Lambda._infer = infer_end 135 nodes.Lambda.infer = infer_end
141 nodes.Const._infer = infer_end 136 nodes.Const.infer = infer_end
142 nodes.List._infer = infer_end 137 nodes.List.infer = infer_end
143 nodes.Tuple._infer = infer_end 138 nodes.Tuple.infer = infer_end
144 nodes.Dict._infer = infer_end 139 nodes.Dict.infer = infer_end
145 nodes.Set._infer = infer_end
146 140
147 def _higher_function_scope(node):
148 """ Search for the first function which encloses the given
149 scope. This can be used for looking up in that function's
150 scope, in case looking up in a lower scope for a particular
151 name fails.
152
153 :param node: A scope node.
154 :returns:
155 ``None``, if no parent function scope was found,
156 otherwise an instance of :class:`astroid.scoped_nodes.Function`,
157 which encloses the given node.
158 """
159 current = node
160 while current.parent and not isinstance(current.parent, nodes.Function):
161 current = current.parent
162 if current and current.parent:
163 return current.parent
164 141
165 def infer_name(self, context=None): 142 def infer_name(self, context=None):
166 """infer a Name: use name lookup rules""" 143 """infer a Name: use name lookup rules"""
167 frame, stmts = self.lookup(self.name) 144 frame, stmts = self.lookup(self.name)
168 if not stmts: 145 if not stmts:
169 # Try to see if the name is enclosed in a nested function 146 raise UnresolvableName(self.name)
170 # and use the higher (first function) scope for searching.
171 # TODO: should this be promoted to other nodes as well?
172 parent_function = _higher_function_scope(self.scope())
173 if parent_function:
174 _, stmts = parent_function.lookup(self.name)
175
176 if not stmts:
177 raise UnresolvableName(self.name)
178 context = context.clone() 147 context = context.clone()
179 context.lookupname = self.name 148 context.lookupname = self.name
180 return _infer_stmts(stmts, context, frame) 149 return _infer_stmts(stmts, context, frame)
181 nodes.Name._infer = path_wrapper(infer_name) 150 nodes.Name.infer = path_wrapper(infer_name)
182 nodes.AssName.infer_lhs = infer_name # won't work with a path wrapper 151 nodes.AssName.infer_lhs = infer_name # won't work with a path wrapper
183 152
184 153
185 def infer_callfunc(self, context=None): 154 def infer_callfunc(self, context=None):
186 """infer a CallFunc node by trying to guess what the function returns""" 155 """infer a CallFunc node by trying to guess what the function returns"""
187 callcontext = context.clone() 156 callcontext = context.clone()
188 callcontext.callcontext = CallContext(self.args, self.starargs, self.kwargs) 157 callcontext.callcontext = CallContext(self.args, self.starargs, self.kwargs)
189 callcontext.boundnode = None 158 callcontext.boundnode = None
190 for callee in self.func.infer(context): 159 for callee in self.func.infer(context):
191 if callee is YES: 160 if callee is YES:
192 yield callee 161 yield callee
193 continue 162 continue
194 try: 163 try:
195 if hasattr(callee, 'infer_call_result'): 164 if hasattr(callee, 'infer_call_result'):
196 for infered in callee.infer_call_result(self, callcontext): 165 for infered in callee.infer_call_result(self, callcontext):
197 yield infered 166 yield infered
198 except InferenceError: 167 except InferenceError:
199 ## XXX log error ? 168 ## XXX log error ?
200 continue 169 continue
201 nodes.CallFunc._infer = path_wrapper(raise_if_nothing_infered(infer_callfunc)) 170 nodes.CallFunc.infer = path_wrapper(raise_if_nothing_infered(infer_callfunc))
202 171
203 172
204 def infer_import(self, context=None, asname=True): 173 def infer_import(self, context=None, asname=True):
205 """infer an Import node: return the imported module/object""" 174 """infer an Import node: return the imported module/object"""
206 name = context.lookupname 175 name = context.lookupname
207 if name is None: 176 if name is None:
208 raise InferenceError() 177 raise InferenceError()
209 if asname: 178 if asname:
210 yield self.do_import_module(self.real_name(name)) 179 yield self.do_import_module(self.real_name(name))
211 else: 180 else:
212 yield self.do_import_module(name) 181 yield self.do_import_module(name)
213 nodes.Import._infer = path_wrapper(infer_import) 182 nodes.Import.infer = path_wrapper(infer_import)
214 183
215 def infer_name_module(self, name): 184 def infer_name_module(self, name):
216 context = InferenceContext() 185 context = InferenceContext()
217 context.lookupname = name 186 context.lookupname = name
218 return self.infer(context, asname=False) 187 return self.infer(context, asname=False)
219 nodes.Import.infer_name_module = infer_name_module 188 nodes.Import.infer_name_module = infer_name_module
220 189
221 190
222 def infer_from(self, context=None, asname=True): 191 def infer_from(self, context=None, asname=True):
223 """infer a From nodes: return the imported module/object""" 192 """infer a From nodes: return the imported module/object"""
224 name = context.lookupname 193 name = context.lookupname
225 if name is None: 194 if name is None:
226 raise InferenceError() 195 raise InferenceError()
227 if asname: 196 if asname:
228 name = self.real_name(name) 197 name = self.real_name(name)
229 module = self.do_import_module() 198 module = self.do_import_module(self.modname)
230 try: 199 try:
231 context = copy_context(context) 200 context = copy_context(context)
232 context.lookupname = name 201 context.lookupname = name
233 return _infer_stmts(module.getattr(name, ignore_locals=module is self.ro ot()), context) 202 return _infer_stmts(module.getattr(name, ignore_locals=module is self.ro ot()), context)
234 except NotFoundError: 203 except NotFoundError:
235 raise InferenceError(name) 204 raise InferenceError(name)
236 nodes.From._infer = path_wrapper(infer_from) 205 nodes.From.infer = path_wrapper(infer_from)
237 206
238 207
239 def infer_getattr(self, context=None): 208 def infer_getattr(self, context=None):
240 """infer a Getattr node by using getattr on the associated object""" 209 """infer a Getattr node by using getattr on the associated object"""
241 #context = context.clone() 210 #context = context.clone()
242 for owner in self.expr.infer(context): 211 for owner in self.expr.infer(context):
243 if owner is YES: 212 if owner is YES:
244 yield owner 213 yield owner
245 continue 214 continue
246 try: 215 try:
247 context.boundnode = owner 216 context.boundnode = owner
248 for obj in owner.igetattr(self.attrname, context): 217 for obj in owner.igetattr(self.attrname, context):
249 yield obj 218 yield obj
250 context.boundnode = None 219 context.boundnode = None
251 except (NotFoundError, InferenceError): 220 except (NotFoundError, InferenceError):
252 context.boundnode = None 221 context.boundnode = None
253 except AttributeError: 222 except AttributeError:
254 # XXX method / function 223 # XXX method / function
255 context.boundnode = None 224 context.boundnode = None
256 nodes.Getattr._infer = path_wrapper(raise_if_nothing_infered(infer_getattr)) 225 nodes.Getattr.infer = path_wrapper(raise_if_nothing_infered(infer_getattr))
257 nodes.AssAttr.infer_lhs = raise_if_nothing_infered(infer_getattr) # # won't work with a path wrapper 226 nodes.AssAttr.infer_lhs = raise_if_nothing_infered(infer_getattr) # # won't work with a path wrapper
258 227
259 228
260 def infer_global(self, context=None): 229 def infer_global(self, context=None):
261 if context.lookupname is None: 230 if context.lookupname is None:
262 raise InferenceError() 231 raise InferenceError()
263 try: 232 try:
264 return _infer_stmts(self.root().getattr(context.lookupname), context) 233 return _infer_stmts(self.root().getattr(context.lookupname), context)
265 except NotFoundError: 234 except NotFoundError:
266 raise InferenceError() 235 raise InferenceError()
267 nodes.Global._infer = path_wrapper(infer_global) 236 nodes.Global.infer = path_wrapper(infer_global)
268 237
269 238
270 def infer_subscript(self, context=None): 239 def infer_subscript(self, context=None):
271 """infer simple subscription such as [1,2,3][0] or (1,2,3)[-1]""" 240 """infer simple subscription such as [1,2,3][0] or (1,2,3)[-1]"""
272 value = self.value.infer(context).next() 241 if isinstance(self.slice, nodes.Index):
273 if value is YES: 242 index = self.slice.value.infer(context).next()
274 yield YES 243 if index is YES:
275 return 244 yield YES
276 245 return
277 index = self.slice.infer(context).next()
278 if index is YES:
279 yield YES
280 return
281
282 if isinstance(index, nodes.Const):
283 try: 246 try:
284 assigned = value.getitem(index.value, context) 247 # suppose it's a Tuple/List node (attribute error else)
248 assigned = self.value.getitem(index.value, context)
285 except AttributeError: 249 except AttributeError:
286 raise InferenceError() 250 raise InferenceError()
287 except (IndexError, TypeError): 251 except (IndexError, TypeError):
288 yield YES 252 yield YES
289 return 253 return
290 for infered in assigned.infer(context): 254 for infered in assigned.infer(context):
291 yield infered 255 yield infered
292 else: 256 else:
293 raise InferenceError() 257 raise InferenceError()
294 nodes.Subscript._infer = path_wrapper(infer_subscript) 258 nodes.Subscript.infer = path_wrapper(infer_subscript)
295 nodes.Subscript.infer_lhs = raise_if_nothing_infered(infer_subscript) 259 nodes.Subscript.infer_lhs = raise_if_nothing_infered(infer_subscript)
296 260
261
262 UNARY_OP_METHOD = {'+': '__pos__',
263 '-': '__neg__',
264 '~': '__invert__',
265 'not': None, # XXX not '__nonzero__'
266 }
267
297 def infer_unaryop(self, context=None): 268 def infer_unaryop(self, context=None):
298 for operand in self.operand.infer(context): 269 for operand in self.operand.infer(context):
299 try: 270 try:
300 yield operand.infer_unary_op(self.op) 271 yield operand.infer_unary_op(self.op)
301 except TypeError: 272 except TypeError:
302 continue 273 continue
303 except AttributeError: 274 except AttributeError:
304 meth = UNARY_OP_METHOD[self.op] 275 meth = UNARY_OP_METHOD[self.op]
305 if meth is None: 276 if meth is None:
306 yield YES 277 yield YES
307 else: 278 else:
308 try: 279 try:
309 # XXX just suppose if the type implement meth, returned type 280 # XXX just suppose if the type implement meth, returned type
310 # will be the same 281 # will be the same
311 operand.getattr(meth) 282 operand.getattr(meth)
312 yield operand 283 yield operand
313 except GeneratorExit: 284 except GeneratorExit:
314 raise 285 raise
315 except: 286 except:
316 yield YES 287 yield YES
317 nodes.UnaryOp._infer = path_wrapper(infer_unaryop) 288 nodes.UnaryOp.infer = path_wrapper(infer_unaryop)
289
290
291 BIN_OP_METHOD = {'+': '__add__',
292 '-': '__sub__',
293 '/': '__div__',
294 '//': '__floordiv__',
295 '*': '__mul__',
296 '**': '__power__',
297 '%': '__mod__',
298 '&': '__and__',
299 '|': '__or__',
300 '^': '__xor__',
301 '<<': '__lshift__',
302 '>>': '__rshift__',
303 }
318 304
319 def _infer_binop(operator, operand1, operand2, context, failures=None): 305 def _infer_binop(operator, operand1, operand2, context, failures=None):
320 if operand1 is YES: 306 if operand1 is YES:
321 yield operand1 307 yield operand1
322 return 308 return
323 try: 309 try:
324 for valnode in operand1.infer_binary_op(operator, operand2, context): 310 for valnode in operand1.infer_binary_op(operator, operand2, context):
325 yield valnode 311 yield valnode
326 except AttributeError: 312 except AttributeError:
327 try: 313 try:
328 # XXX just suppose if the type implement meth, returned type 314 # XXX just suppose if the type implement meth, returned type
329 # will be the same 315 # will be the same
330 operand1.getattr(BIN_OP_METHOD[operator]) 316 operand1.getattr(BIN_OP_METHOD[operator])
331 yield operand1 317 yield operand1
332 except: 318 except:
333 if failures is None: 319 if failures is None:
334 yield YES 320 yield YES
335 else: 321 else:
336 failures.append(operand1) 322 failures.append(operand1)
337 323
338 def infer_binop(self, context=None): 324 def infer_binop(self, context=None):
339 failures = [] 325 failures = []
340 for lhs in self.left.infer(context): 326 for lhs in self.left.infer(context):
341 for val in _infer_binop(self.op, lhs, self.right, context, failures): 327 for val in _infer_binop(self.op, lhs, self.right, context, failures):
342 yield val 328 yield val
343 for lhs in failures: 329 for lhs in failures:
344 for rhs in self.right.infer(context): 330 for rhs in self.right.infer(context):
345 for val in _infer_binop(self.op, rhs, lhs, context): 331 for val in _infer_binop(self.op, rhs, lhs, context):
346 yield val 332 yield val
347 nodes.BinOp._infer = path_wrapper(infer_binop) 333 nodes.BinOp.infer = path_wrapper(infer_binop)
348 334
349 335
350 def infer_arguments(self, context=None): 336 def infer_arguments(self, context=None):
351 name = context.lookupname 337 name = context.lookupname
352 if name is None: 338 if name is None:
353 raise InferenceError() 339 raise InferenceError()
354 return _arguments_infer_argname(self, name, context) 340 return _arguments_infer_argname(self, name, context)
355 nodes.Arguments._infer = infer_arguments 341 nodes.Arguments.infer = infer_arguments
356 342
357 343
358 def infer_ass(self, context=None): 344 def infer_ass(self, context=None):
359 """infer a AssName/AssAttr: need to inspect the RHS part of the 345 """infer a AssName/AssAttr: need to inspect the RHS part of the
360 assign node 346 assign node
361 """ 347 """
362 stmt = self.statement() 348 stmt = self.statement()
363 if isinstance(stmt, nodes.AugAssign): 349 if isinstance(stmt, nodes.AugAssign):
364 return stmt.infer(context) 350 return stmt.infer(context)
365 stmts = list(self.assigned_stmts(context=context)) 351 stmts = list(self.assigned_stmts(context=context))
366 return _infer_stmts(stmts, context) 352 return _infer_stmts(stmts, context)
367 nodes.AssName._infer = path_wrapper(infer_ass) 353 nodes.AssName.infer = path_wrapper(infer_ass)
368 nodes.AssAttr._infer = path_wrapper(infer_ass) 354 nodes.AssAttr.infer = path_wrapper(infer_ass)
369 355
370 def infer_augassign(self, context=None): 356 def infer_augassign(self, context=None):
371 failures = [] 357 failures = []
372 for lhs in self.target.infer_lhs(context): 358 for lhs in self.target.infer_lhs(context):
373 for val in _infer_binop(self.op, lhs, self.value, context, failures): 359 for val in _infer_binop(self.op, lhs, self.value, context, failures):
374 yield val 360 yield val
375 for lhs in failures: 361 for lhs in failures:
376 for rhs in self.value.infer(context): 362 for rhs in self.value.infer(context):
377 for val in _infer_binop(self.op, rhs, lhs, context): 363 for val in _infer_binop(self.op, rhs, lhs, context):
378 yield val 364 yield val
379 nodes.AugAssign._infer = path_wrapper(infer_augassign) 365 nodes.AugAssign.infer = path_wrapper(infer_augassign)
380 366
381 367
382 # no infer method on DelName and DelAttr (expected InferenceError) 368 # no infer method on DelName and DelAttr (expected InferenceError)
383 369
384 370
385 def infer_empty_node(self, context=None): 371 def infer_empty_node(self, context=None):
386 if not self.has_underlying_object(): 372 if not self.has_underlying_object():
387 yield YES 373 yield YES
388 else: 374 else:
389 try: 375 try:
390 for infered in MANAGER.infer_ast_from_something(self.object, 376 for infered in MANAGER.infer_astng_from_something(self.object,
391 context=context): 377 context=context):
392 yield infered 378 yield infered
393 except AstroidError: 379 except ASTNGError:
394 yield YES 380 yield YES
395 nodes.EmptyNode._infer = path_wrapper(infer_empty_node) 381 nodes.EmptyNode.infer = path_wrapper(infer_empty_node)
396 382
397
398 def infer_index(self, context=None):
399 return self.value.infer(context)
400 nodes.Index._infer = infer_index
OLDNEW
« no previous file with comments | « third_party/logilab/astng/exceptions.py ('k') | third_party/logilab/astng/inspector.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698