| OLD | NEW |
| (Empty) |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 # This module's classes provide an interface to mojo modules. Modules are | |
| 6 # collections of interfaces and structs to be used by mojo ipc clients and | |
| 7 # servers. | |
| 8 # | |
| 9 # A simple interface would be created this way: | |
| 10 # module = mojom.generate.module.Module('Foo') | |
| 11 # interface = module.AddInterface('Bar') | |
| 12 # method = interface.AddMethod('Tat', 0) | |
| 13 # method.AddParameter('baz', 0, mojom.INT32) | |
| 14 | |
| 15 | |
| 16 class Kind(object): | |
| 17 def __init__(self, spec=None): | |
| 18 self.spec = spec | |
| 19 self.parent_kind = None | |
| 20 | |
| 21 | |
| 22 class ReferenceKind(Kind): | |
| 23 """ReferenceKind represents pointer types and handle types. | |
| 24 A type is nullable if null (for pointer types) or invalid handle (for handle | |
| 25 types) is a legal value for the type. | |
| 26 """ | |
| 27 | |
| 28 def __init__(self, spec=None, is_nullable=False): | |
| 29 assert spec is None or is_nullable == spec.startswith('?') | |
| 30 Kind.__init__(self, spec) | |
| 31 self.is_nullable = is_nullable | |
| 32 self.shared_definition = {} | |
| 33 | |
| 34 def MakeNullableKind(self): | |
| 35 assert not self.is_nullable | |
| 36 | |
| 37 if self == STRING: | |
| 38 return NULLABLE_STRING | |
| 39 if self == HANDLE: | |
| 40 return NULLABLE_HANDLE | |
| 41 if self == DCPIPE: | |
| 42 return NULLABLE_DCPIPE | |
| 43 if self == DPPIPE: | |
| 44 return NULLABLE_DPPIPE | |
| 45 if self == MSGPIPE: | |
| 46 return NULLABLE_MSGPIPE | |
| 47 if self == SHAREDBUFFER: | |
| 48 return NULLABLE_SHAREDBUFFER | |
| 49 | |
| 50 nullable_kind = type(self)() | |
| 51 nullable_kind.shared_definition = self.shared_definition | |
| 52 if self.spec is not None: | |
| 53 nullable_kind.spec = '?' + self.spec | |
| 54 nullable_kind.is_nullable = True | |
| 55 | |
| 56 return nullable_kind | |
| 57 | |
| 58 @classmethod | |
| 59 def AddSharedProperty(cls, name): | |
| 60 """Adds a property |name| to |cls|, which accesses the corresponding item in | |
| 61 |shared_definition|. | |
| 62 | |
| 63 The reason of adding such indirection is to enable sharing definition | |
| 64 between a reference kind and its nullable variation. For example: | |
| 65 a = Struct('test_struct_1') | |
| 66 b = a.MakeNullableKind() | |
| 67 a.name = 'test_struct_2' | |
| 68 print b.name # Outputs 'test_struct_2'. | |
| 69 """ | |
| 70 def Get(self): | |
| 71 return self.shared_definition[name] | |
| 72 | |
| 73 def Set(self, value): | |
| 74 self.shared_definition[name] = value | |
| 75 | |
| 76 setattr(cls, name, property(Get, Set)) | |
| 77 | |
| 78 | |
| 79 # Initialize the set of primitive types. These can be accessed by clients. | |
| 80 BOOL = Kind('b') | |
| 81 INT8 = Kind('i8') | |
| 82 INT16 = Kind('i16') | |
| 83 INT32 = Kind('i32') | |
| 84 INT64 = Kind('i64') | |
| 85 UINT8 = Kind('u8') | |
| 86 UINT16 = Kind('u16') | |
| 87 UINT32 = Kind('u32') | |
| 88 UINT64 = Kind('u64') | |
| 89 FLOAT = Kind('f') | |
| 90 DOUBLE = Kind('d') | |
| 91 STRING = ReferenceKind('s') | |
| 92 HANDLE = ReferenceKind('h') | |
| 93 DCPIPE = ReferenceKind('h:d:c') | |
| 94 DPPIPE = ReferenceKind('h:d:p') | |
| 95 MSGPIPE = ReferenceKind('h:m') | |
| 96 SHAREDBUFFER = ReferenceKind('h:s') | |
| 97 NULLABLE_STRING = ReferenceKind('?s', True) | |
| 98 NULLABLE_HANDLE = ReferenceKind('?h', True) | |
| 99 NULLABLE_DCPIPE = ReferenceKind('?h:d:c', True) | |
| 100 NULLABLE_DPPIPE = ReferenceKind('?h:d:p', True) | |
| 101 NULLABLE_MSGPIPE = ReferenceKind('?h:m', True) | |
| 102 NULLABLE_SHAREDBUFFER = ReferenceKind('?h:s', True) | |
| 103 | |
| 104 | |
| 105 # Collection of all Primitive types | |
| 106 PRIMITIVES = ( | |
| 107 BOOL, | |
| 108 INT8, | |
| 109 INT16, | |
| 110 INT32, | |
| 111 INT64, | |
| 112 UINT8, | |
| 113 UINT16, | |
| 114 UINT32, | |
| 115 UINT64, | |
| 116 FLOAT, | |
| 117 DOUBLE, | |
| 118 STRING, | |
| 119 HANDLE, | |
| 120 DCPIPE, | |
| 121 DPPIPE, | |
| 122 MSGPIPE, | |
| 123 SHAREDBUFFER, | |
| 124 NULLABLE_STRING, | |
| 125 NULLABLE_HANDLE, | |
| 126 NULLABLE_DCPIPE, | |
| 127 NULLABLE_DPPIPE, | |
| 128 NULLABLE_MSGPIPE, | |
| 129 NULLABLE_SHAREDBUFFER | |
| 130 ) | |
| 131 | |
| 132 | |
| 133 class NamedValue(object): | |
| 134 def __init__(self, module=None, parent_kind=None, name=None): | |
| 135 self.module = module | |
| 136 if module: | |
| 137 self.namespace = module.namespace | |
| 138 self.parent_kind = parent_kind | |
| 139 self.name = name | |
| 140 self.imported_from = None | |
| 141 | |
| 142 def GetSpec(self): | |
| 143 return (self.namespace + '.' + | |
| 144 (self.parent_kind and (self.parent_kind.name + '.') or "") + | |
| 145 self.name) | |
| 146 | |
| 147 | |
| 148 class BuiltinValue(object): | |
| 149 def __init__(self, value): | |
| 150 self.value = value | |
| 151 | |
| 152 | |
| 153 class ConstantValue(NamedValue): | |
| 154 def __init__(self, module=None, parent_kind=None, constant=None): | |
| 155 if constant: | |
| 156 NamedValue.__init__(self, module, parent_kind, constant.name) | |
| 157 else: | |
| 158 NamedValue.__init__(self, module, parent_kind) | |
| 159 self.constant = constant | |
| 160 | |
| 161 | |
| 162 class EnumValue(NamedValue): | |
| 163 def __init__(self, module=None, enum=None, field=None): | |
| 164 if enum and field: | |
| 165 NamedValue.__init__(self, module, enum.parent_kind, field.name) | |
| 166 else: | |
| 167 NamedValue.__init__(self, module) | |
| 168 self.enum = enum | |
| 169 | |
| 170 def GetSpec(self): | |
| 171 return (self.namespace + '.' + | |
| 172 (self.parent_kind and (self.parent_kind.name + '.') or "") + | |
| 173 self.enum.name + '.' + self.name) | |
| 174 | |
| 175 | |
| 176 class Constant(object): | |
| 177 def __init__(self, name=None, kind=None, value=None, parent_kind=None): | |
| 178 self.name = name | |
| 179 self.kind = kind | |
| 180 self.value = value | |
| 181 self.parent_kind = parent_kind | |
| 182 | |
| 183 | |
| 184 class Field(object): | |
| 185 def __init__(self, name=None, kind=None, ordinal=None, default=None, | |
| 186 attributes=None): | |
| 187 if self.__class__.__name__ == 'Field': | |
| 188 raise Exception() | |
| 189 self.name = name | |
| 190 self.kind = kind | |
| 191 self.ordinal = ordinal | |
| 192 self.default = default | |
| 193 self.attributes = attributes | |
| 194 | |
| 195 | |
| 196 class StructField(Field): pass | |
| 197 | |
| 198 | |
| 199 class UnionField(Field): pass | |
| 200 | |
| 201 | |
| 202 class Struct(ReferenceKind): | |
| 203 ReferenceKind.AddSharedProperty('name') | |
| 204 ReferenceKind.AddSharedProperty('module') | |
| 205 ReferenceKind.AddSharedProperty('imported_from') | |
| 206 ReferenceKind.AddSharedProperty('fields') | |
| 207 ReferenceKind.AddSharedProperty('attributes') | |
| 208 ReferenceKind.AddSharedProperty('constants') | |
| 209 ReferenceKind.AddSharedProperty('enums') | |
| 210 ReferenceKind.AddSharedProperty('type_key') | |
| 211 | |
| 212 def __init__(self, name=None, module=None, attributes=None): | |
| 213 if name is not None: | |
| 214 spec = 'x:' + name | |
| 215 else: | |
| 216 spec = None | |
| 217 ReferenceKind.__init__(self, spec) | |
| 218 self.name = name | |
| 219 self.module = module | |
| 220 self.imported_from = None | |
| 221 self.fields = [] | |
| 222 self.constants = [] | |
| 223 self.enums = [] | |
| 224 self.attributes = attributes | |
| 225 | |
| 226 def AddField(self, name, kind, ordinal=None, default=None, attributes=None): | |
| 227 field = StructField(name, kind, ordinal, default, attributes) | |
| 228 self.fields.append(field) | |
| 229 return field | |
| 230 | |
| 231 | |
| 232 class Union(ReferenceKind): | |
| 233 ReferenceKind.AddSharedProperty('name') | |
| 234 ReferenceKind.AddSharedProperty('module') | |
| 235 ReferenceKind.AddSharedProperty('imported_from') | |
| 236 ReferenceKind.AddSharedProperty('fields') | |
| 237 ReferenceKind.AddSharedProperty('attributes') | |
| 238 ReferenceKind.AddSharedProperty('type_key') | |
| 239 | |
| 240 def __init__(self, name=None, module=None, attributes=None): | |
| 241 if name is not None: | |
| 242 spec = 'x:' + name | |
| 243 else: | |
| 244 spec = None | |
| 245 ReferenceKind.__init__(self, spec) | |
| 246 self.name = name | |
| 247 self.module = module | |
| 248 self.imported_from = None | |
| 249 self.fields = [] | |
| 250 self.attributes = attributes | |
| 251 | |
| 252 def AddField(self, name, kind, ordinal=None, attributes=None): | |
| 253 field = UnionField(name, kind, ordinal, None, attributes) | |
| 254 self.fields.append(field) | |
| 255 return field | |
| 256 | |
| 257 | |
| 258 class Array(ReferenceKind): | |
| 259 ReferenceKind.AddSharedProperty('kind') | |
| 260 ReferenceKind.AddSharedProperty('length') | |
| 261 | |
| 262 def __init__(self, kind=None, length=None): | |
| 263 if kind is not None: | |
| 264 if length is not None: | |
| 265 spec = 'a%d:%s' % (length, kind.spec) | |
| 266 else: | |
| 267 spec = 'a:%s' % kind.spec | |
| 268 | |
| 269 ReferenceKind.__init__(self, spec) | |
| 270 else: | |
| 271 ReferenceKind.__init__(self) | |
| 272 self.kind = kind | |
| 273 self.length = length | |
| 274 | |
| 275 | |
| 276 class Map(ReferenceKind): | |
| 277 ReferenceKind.AddSharedProperty('key_kind') | |
| 278 ReferenceKind.AddSharedProperty('value_kind') | |
| 279 | |
| 280 def __init__(self, key_kind=None, value_kind=None): | |
| 281 if (key_kind is not None and value_kind is not None): | |
| 282 ReferenceKind.__init__(self, | |
| 283 'm[' + key_kind.spec + '][' + value_kind.spec + | |
| 284 ']') | |
| 285 if IsNullableKind(key_kind): | |
| 286 raise Exception("Nullable kinds cannot be keys in maps.") | |
| 287 if IsStructKind(key_kind): | |
| 288 # TODO(erg): It would sometimes be nice if we could key on struct | |
| 289 # values. However, what happens if the struct has a handle in it? Or | |
| 290 # non-copyable data like an array? | |
| 291 raise Exception("Structs cannot be keys in maps.") | |
| 292 if IsAnyHandleKind(key_kind): | |
| 293 raise Exception("Handles cannot be keys in maps.") | |
| 294 if IsInterfaceKind(key_kind): | |
| 295 raise Exception("Interfaces cannot be keys in maps.") | |
| 296 if IsArrayKind(key_kind): | |
| 297 raise Exception("Arrays cannot be keys in maps.") | |
| 298 else: | |
| 299 ReferenceKind.__init__(self) | |
| 300 | |
| 301 self.key_kind = key_kind | |
| 302 self.value_kind = value_kind | |
| 303 | |
| 304 | |
| 305 class InterfaceRequest(ReferenceKind): | |
| 306 ReferenceKind.AddSharedProperty('kind') | |
| 307 | |
| 308 def __init__(self, kind=None): | |
| 309 if kind is not None: | |
| 310 if not isinstance(kind, Interface): | |
| 311 raise Exception( | |
| 312 "Interface request requires %r to be an interface." % kind.spec) | |
| 313 ReferenceKind.__init__(self, 'r:' + kind.spec) | |
| 314 else: | |
| 315 ReferenceKind.__init__(self) | |
| 316 self.kind = kind | |
| 317 | |
| 318 | |
| 319 class Parameter(object): | |
| 320 def __init__(self, name=None, kind=None, ordinal=None, default=None, | |
| 321 attributes=None): | |
| 322 self.name = name | |
| 323 self.ordinal = ordinal | |
| 324 self.kind = kind | |
| 325 self.default = default | |
| 326 self.attributes = attributes | |
| 327 | |
| 328 | |
| 329 class Method(object): | |
| 330 def __init__(self, interface, name, ordinal=None, attributes=None): | |
| 331 self.interface = interface | |
| 332 self.name = name | |
| 333 self.ordinal = ordinal | |
| 334 self.parameters = [] | |
| 335 self.response_parameters = None | |
| 336 self.attributes = attributes | |
| 337 | |
| 338 def AddParameter(self, name, kind, ordinal=None, default=None, | |
| 339 attributes=None): | |
| 340 parameter = Parameter(name, kind, ordinal, default, attributes) | |
| 341 self.parameters.append(parameter) | |
| 342 return parameter | |
| 343 | |
| 344 def AddResponseParameter(self, name, kind, ordinal=None, default=None, | |
| 345 attributes=None): | |
| 346 if self.response_parameters == None: | |
| 347 self.response_parameters = [] | |
| 348 parameter = Parameter(name, kind, ordinal, default, attributes) | |
| 349 self.response_parameters.append(parameter) | |
| 350 return parameter | |
| 351 | |
| 352 | |
| 353 class Interface(ReferenceKind): | |
| 354 ReferenceKind.AddSharedProperty('module') | |
| 355 ReferenceKind.AddSharedProperty('name') | |
| 356 ReferenceKind.AddSharedProperty('imported_from') | |
| 357 ReferenceKind.AddSharedProperty('methods') | |
| 358 ReferenceKind.AddSharedProperty('attributes') | |
| 359 ReferenceKind.AddSharedProperty('constants') | |
| 360 ReferenceKind.AddSharedProperty('enums') | |
| 361 ReferenceKind.AddSharedProperty('type_key') | |
| 362 | |
| 363 def __init__(self, name=None, module=None, attributes=None): | |
| 364 if name is not None: | |
| 365 spec = 'x:' + name | |
| 366 else: | |
| 367 spec = None | |
| 368 ReferenceKind.__init__(self, spec) | |
| 369 self.module = module | |
| 370 self.name = name | |
| 371 self.imported_from = None | |
| 372 self.methods = [] | |
| 373 self.enums = [] | |
| 374 self.constants = [] | |
| 375 self.attributes = attributes | |
| 376 | |
| 377 def AddMethod(self, name, ordinal=None, attributes=None): | |
| 378 method = Method(self, name, ordinal, attributes) | |
| 379 self.methods.append(method) | |
| 380 return method | |
| 381 | |
| 382 # TODO(451323): Remove when the language backends no longer rely on this. | |
| 383 @property | |
| 384 def client(self): | |
| 385 return None | |
| 386 | |
| 387 | |
| 388 class EnumField(object): | |
| 389 def __init__(self, name=None, value=None, attributes=None, | |
| 390 numeric_value=None): | |
| 391 self.name = name | |
| 392 self.value = value | |
| 393 self.attributes = attributes | |
| 394 self.numeric_value = numeric_value | |
| 395 | |
| 396 | |
| 397 class Enum(Kind): | |
| 398 def __init__(self, name=None, module=None, attributes=None): | |
| 399 self.module = module | |
| 400 self.name = name | |
| 401 self.imported_from = None | |
| 402 if name is not None: | |
| 403 spec = 'x:' + name | |
| 404 else: | |
| 405 spec = None | |
| 406 Kind.__init__(self, spec) | |
| 407 self.fields = [] | |
| 408 self.attributes = attributes | |
| 409 | |
| 410 | |
| 411 class Module(object): | |
| 412 def __init__(self, name=None, namespace=None, attributes=None): | |
| 413 self.name = name | |
| 414 self.path = name | |
| 415 self.namespace = namespace | |
| 416 self.structs = [] | |
| 417 self.unions = [] | |
| 418 self.interfaces = [] | |
| 419 self.enums = [] | |
| 420 self.constants = [] | |
| 421 self.kinds = {} | |
| 422 self.attributes = attributes | |
| 423 self.imports = [] | |
| 424 # Transitive imports should contain all of the module's imports plus | |
| 425 # all of their imports recursively. | |
| 426 self.transitive_imports = [] | |
| 427 | |
| 428 def AddInterface(self, name, attributes=None): | |
| 429 interface = Interface(name, self, attributes) | |
| 430 self.interfaces.append(interface) | |
| 431 return interface | |
| 432 | |
| 433 def AddStruct(self, name, attributes=None): | |
| 434 struct = Struct(name, self, attributes) | |
| 435 self.structs.append(struct) | |
| 436 return struct | |
| 437 | |
| 438 def AddUnion(self, name, attributes=None): | |
| 439 union = Union(name, self, attributes) | |
| 440 self.unions.append(union) | |
| 441 return union | |
| 442 | |
| 443 def GetPackageName(kind): | |
| 444 """Get the package name from the given kind's module.""" | |
| 445 return kind.module.name.split('.')[0] | |
| 446 | |
| 447 def IsBoolKind(kind): | |
| 448 return kind.spec == BOOL.spec | |
| 449 | |
| 450 | |
| 451 def IsFloatKind(kind): | |
| 452 return kind.spec == FLOAT.spec | |
| 453 | |
| 454 | |
| 455 def IsDoubleKind(kind): | |
| 456 return kind.spec == DOUBLE.spec | |
| 457 | |
| 458 | |
| 459 def IsIntegralKind(kind): | |
| 460 return (kind.spec == BOOL.spec or | |
| 461 kind.spec == INT8.spec or | |
| 462 kind.spec == INT16.spec or | |
| 463 kind.spec == INT32.spec or | |
| 464 kind.spec == INT64.spec or | |
| 465 kind.spec == UINT8.spec or | |
| 466 kind.spec == UINT16.spec or | |
| 467 kind.spec == UINT32.spec or | |
| 468 kind.spec == UINT64.spec) | |
| 469 | |
| 470 def IsNumericalKind(kind): | |
| 471 return (IsBoolKind(kind) or | |
| 472 IsFloatKind(kind) or | |
| 473 IsDoubleKind(kind) or | |
| 474 IsIntegralKind(kind)) | |
| 475 | |
| 476 def IsStringKind(kind): | |
| 477 return kind.spec == STRING.spec or kind.spec == NULLABLE_STRING.spec | |
| 478 | |
| 479 | |
| 480 def IsGenericHandleKind(kind): | |
| 481 return kind.spec == HANDLE.spec or kind.spec == NULLABLE_HANDLE.spec | |
| 482 | |
| 483 | |
| 484 def IsDataPipeConsumerKind(kind): | |
| 485 return kind.spec == DCPIPE.spec or kind.spec == NULLABLE_DCPIPE.spec | |
| 486 | |
| 487 | |
| 488 def IsDataPipeProducerKind(kind): | |
| 489 return kind.spec == DPPIPE.spec or kind.spec == NULLABLE_DPPIPE.spec | |
| 490 | |
| 491 | |
| 492 def IsMessagePipeKind(kind): | |
| 493 return kind.spec == MSGPIPE.spec or kind.spec == NULLABLE_MSGPIPE.spec | |
| 494 | |
| 495 | |
| 496 def IsSharedBufferKind(kind): | |
| 497 return (kind.spec == SHAREDBUFFER.spec or | |
| 498 kind.spec == NULLABLE_SHAREDBUFFER.spec) | |
| 499 | |
| 500 | |
| 501 def IsStructKind(kind): | |
| 502 return isinstance(kind, Struct) | |
| 503 | |
| 504 | |
| 505 def IsUnionKind(kind): | |
| 506 return isinstance(kind, Union) | |
| 507 | |
| 508 | |
| 509 def IsArrayKind(kind): | |
| 510 return isinstance(kind, Array) | |
| 511 | |
| 512 | |
| 513 def IsInterfaceKind(kind): | |
| 514 return isinstance(kind, Interface) | |
| 515 | |
| 516 | |
| 517 def IsInterfaceRequestKind(kind): | |
| 518 return isinstance(kind, InterfaceRequest) | |
| 519 | |
| 520 | |
| 521 def IsEnumKind(kind): | |
| 522 return isinstance(kind, Enum) | |
| 523 | |
| 524 | |
| 525 def IsReferenceKind(kind): | |
| 526 return isinstance(kind, ReferenceKind) | |
| 527 | |
| 528 | |
| 529 def IsNullableKind(kind): | |
| 530 return IsReferenceKind(kind) and kind.is_nullable | |
| 531 | |
| 532 | |
| 533 def IsMapKind(kind): | |
| 534 return isinstance(kind, Map) | |
| 535 | |
| 536 | |
| 537 def IsObjectKind(kind): | |
| 538 return IsPointerKind(kind) or IsUnionKind(kind) | |
| 539 | |
| 540 | |
| 541 def IsPointerKind(kind): | |
| 542 return (IsStructKind(kind) or IsArrayKind(kind) or IsStringKind(kind) or | |
| 543 IsMapKind(kind)) | |
| 544 | |
| 545 | |
| 546 # Please note that interface is not considered as handle kind, since it is an | |
| 547 # aggregate type consisting of a handle and a version number. | |
| 548 def IsAnyHandleKind(kind): | |
| 549 return (IsGenericHandleKind(kind) or | |
| 550 IsDataPipeConsumerKind(kind) or | |
| 551 IsDataPipeProducerKind(kind) or | |
| 552 IsMessagePipeKind(kind) or | |
| 553 IsSharedBufferKind(kind) or | |
| 554 IsInterfaceRequestKind(kind)) | |
| 555 | |
| 556 | |
| 557 def IsMoveOnlyKind(kind): | |
| 558 return (not IsStringKind(kind) and IsObjectKind(kind)) or \ | |
| 559 IsAnyHandleKind(kind) or IsInterfaceKind(kind) | |
| 560 | |
| 561 | |
| 562 def ContainsHandles(kind, visited_kinds): | |
| 563 if kind in visited_kinds: | |
| 564 # No need to examine the kind again. | |
| 565 return False | |
| 566 visited_kinds.add(kind) | |
| 567 if IsAnyHandleKind(kind) or IsInterfaceKind(kind): | |
| 568 return True | |
| 569 if IsArrayKind(kind): | |
| 570 return ContainsHandles(kind.kind, visited_kinds) | |
| 571 if IsStructKind(kind) or IsUnionKind(kind): | |
| 572 for field in kind.fields: | |
| 573 if ContainsHandles(field.kind, visited_kinds): | |
| 574 return True | |
| 575 if IsMapKind(kind): | |
| 576 # No need to examine the key kind, only primitive kinds and non-nullable | |
| 577 # string are allowed to be key kinds. | |
| 578 return ContainsHandles(kind.value_kind, visited_kinds) | |
| 579 return False | |
| 580 | |
| 581 | |
| 582 def IsCloneableKind(kind): | |
| 583 return not ContainsHandles(kind, set()) | |
| 584 | |
| 585 | |
| 586 def HasCallbacks(interface): | |
| 587 for method in interface.methods: | |
| 588 if method.response_parameters != None: | |
| 589 return True | |
| 590 return False | |
| OLD | NEW |