| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 27647e1 commit e189ca7
1 file changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -5,7 +5,8 @@ | |||
| 5 | 5 | ||
| 6 | 6 | # This script generates Lib/snippets/whats_left_data.py with these variables defined: | |
| 7 | 7 | # expected_methods - a dictionary mapping builtin objects to their methods | |
| 8 | - # cpymods - a dictionary mapping module names to their contents | ||
| 8 | + # cpymods - a dictionary mapping module names to their contents, including | ||
| 9 | + # the members of native classes as "Class.member" | ||
| 9 | 10 | # libdir - the location of RustPython's Lib/ directory. | |
| 10 | 11 | ||
| 11 | 12 | # | |
@@ -24,6 +25,7 @@ | |||
| 24 | 25 | import re | |
| 25 | 26 | import subprocess | |
| 26 | 27 | import sys | |
| 28 | + import types | ||
| 27 | 29 | import warnings | |
| 28 | 30 | from pydoc import ModuleScanner | |
| 29 | 31 | ||
@@ -109,12 +111,11 @@ def attr_is_not_inherited(type_, attr): | |||
| 109 | 111 | """ | |
| 110 | 112 | returns True if type_'s attr is not inherited from any of its base classes | |
| 111 | 113 | """ | |
| 112 | - bases = type_.__mro__[1:] | ||
| 113 | - return getattr(type_, attr) not in (getattr(base, attr, None) for base in bases) | ||
| 114 | + return attr in type_.__dict__ | ||
| 114 | 115 | ||
| 115 | 116 | ||
| 116 | 117 | def extra_info(obj): | |
| 117 | - if callable(obj) and not inspect._signature_is_builtin(obj): | ||
| 118 | + if callable(obj): | ||
| 118 | 119 | doc = inspect.getdoc(obj) | |
| 119 | 120 | try: | |
| 120 | 121 | sig = str(inspect.signature(obj)) | |
@@ -151,36 +152,70 @@ def name_sort_key(name): | |||
| 151 | 152 | return name + "2" | |
| 152 | 153 | ||
| 153 | 154 | ||
| 155 | + BUILTIN_TYPES = [ | ||
| 156 | + bool, | ||
| 157 | + bytearray, | ||
| 158 | + bytes, | ||
| 159 | + complex, | ||
| 160 | + dict, | ||
| 161 | + enumerate, | ||
| 162 | + filter, | ||
| 163 | + float, | ||
| 164 | + frozenset, | ||
| 165 | + int, | ||
| 166 | + list, | ||
| 167 | + map, | ||
| 168 | + memoryview, | ||
| 169 | + range, | ||
| 170 | + set, | ||
| 171 | + slice, | ||
| 172 | + str, | ||
| 173 | + super, | ||
| 174 | + tuple, | ||
| 175 | + object, | ||
| 176 | + zip, | ||
| 177 | + classmethod, | ||
| 178 | + staticmethod, | ||
| 179 | + property, | ||
| 180 | + Exception, | ||
| 181 | + BaseException, | ||
| 182 | + ] | ||
| 183 | + | ||
| 184 | + | ||
| 185 | + # CPython's per-class annotations cache and its vectorcall C API offset | ||
| 186 | + CPYTHON_INTERNAL_ATTRS = {"__annotations_cache__", "__vectorcalloffset__"} | ||
| 187 | + | ||
| 188 | + | ||
| 189 | + def own_attrs(typ): | ||
| 190 | + attrs = [] | ||
| 191 | + for attr in dir(typ): | ||
| 192 | + if attr in CPYTHON_INTERNAL_ATTRS: | ||
| 193 | + continue | ||
| 194 | + # Skip attributes in dir() but not actually accessible (e.g., descriptor that raises) | ||
| 195 | + if not hasattr(typ, attr): | ||
| 196 | + continue | ||
| 197 | + if attr_is_not_inherited(typ, attr): | ||
| 198 | + attrs.append((attr, extra_info(getattr(typ, attr)))) | ||
| 199 | + return attrs | ||
| 200 | + | ||
| 201 | + | ||
| 202 | + def is_native_class(obj): | ||
| 203 | + # A class statement records __firstlineno__. A class made by calling | ||
| 204 | + # type(), like a namedtuple, still has Python functions. | ||
| 205 | + if not isinstance(obj, type) or "__firstlineno__" in obj.__dict__: | ||
| 206 | + return False | ||
| 207 | + for value in obj.__dict__.values(): | ||
| 208 | + if isinstance(value, (classmethod, staticmethod)): | ||
| 209 | + value = value.__func__ | ||
| 210 | + if isinstance(value, property): | ||
| 211 | + value = value.fget | ||
| 212 | + if isinstance(value, types.FunctionType): | ||
| 213 | + return False | ||
| 214 | + return True | ||
| 215 | + | ||
| 216 | + | ||
| 154 | 217 | def gen_methods(): | |
| 155 | - types = [ | ||
| 156 | - bool, | ||
| 157 | - bytearray, | ||
| 158 | - bytes, | ||
| 159 | - complex, | ||
| 160 | - dict, | ||
| 161 | - enumerate, | ||
| 162 | - filter, | ||
| 163 | - float, | ||
| 164 | - frozenset, | ||
| 165 | - int, | ||
| 166 | - list, | ||
| 167 | - map, | ||
| 168 | - memoryview, | ||
| 169 | - range, | ||
| 170 | - set, | ||
| 171 | - slice, | ||
| 172 | - str, | ||
| 173 | - super, | ||
| 174 | - tuple, | ||
| 175 | - object, | ||
| 176 | - zip, | ||
| 177 | - classmethod, | ||
| 178 | - staticmethod, | ||
| 179 | - property, | ||
| 180 | - Exception, | ||
| 181 | - BaseException, | ||
| 182 | - ] | ||
| 183 | - objects = [t.__name__ for t in types] | ||
| 218 | + objects = [t.__name__ for t in BUILTIN_TYPES] | ||
| 184 | 219 | objects.append("type(None)") | |
| 185 | 220 | ||
| 186 | 221 | iters = [ | |
@@ -202,14 +237,7 @@ def gen_methods(): | |||
| 202 | 237 | methods = {} | |
| 203 | 238 | for typ_code in objects + iters: | |
| 204 | 239 | typ = eval(typ_code) | |
| 205 | - attrs = [] | ||
| 206 | - for attr in dir(typ): | ||
| 207 | - # Skip attributes in dir() but not actually accessible (e.g., descriptor that raises) | ||
| 208 | - if not hasattr(typ, attr): | ||
| 209 | - continue | ||
| 210 | - if attr_is_not_inherited(typ, attr): | ||
| 211 | - attrs.append((attr, extra_info(getattr(typ, attr)))) | ||
| 212 | - methods[typ.__name__] = (typ_code, extra_info(typ), attrs) | ||
| 240 | + methods[typ.__name__] = (typ_code, extra_info(typ), own_attrs(typ)) | ||
| 213 | 241 | ||
| 214 | 242 | output = "expected_methods = {\n" | |
| 215 | 243 | for name in sorted(methods.keys(), key=name_sort_key): | |
@@ -272,9 +300,21 @@ def is_child(module, item): | |||
| 272 | 300 | ||
| 273 | 301 | def dir_of_mod_or_error(module_name, keep_other=True): | |
| 274 | 302 | module = import_module(module_name) | |
| 303 | + if isinstance(module, Exception): | ||
| 304 | + return module | ||
| 275 | 305 | item_names = sorted(set(dir(module))) | |
| 276 | 306 | result = {} | |
| 277 | 307 | for item_name in item_names: | |
| 308 | + # eval() adds __builtins__ to its globals, and inspect.signature() evals | ||
| 309 | + # defaults in the module's namespace, so whether it exists depends on | ||
| 310 | + # what was inspected before. | ||
| 311 | + if item_name == "__builtins__": | ||
| 312 | + continue | ||
| 313 | + if item_name == "__doc__": | ||
| 314 | + # extra_info() reports a docstring for a callable only, and a module | ||
| 315 | + # is not one. getdoc() matches how a callable's is normalized. | ||
| 316 | + result[item_name] = {"sig": None, "doc": inspect.getdoc(module)} | ||
| 317 | + continue | ||
| 278 | 318 | item = getattr(module, item_name) | |
| 279 | 319 | # don't repeat items imported from other modules | |
| 280 | 320 | if keep_other or is_child(module, item) or inspect.getmodule(item) is None: | |
@@ -297,6 +337,17 @@ def gen_modules(): | |||
| 297 | 337 | file=sys.stderr, | |
| 298 | 338 | ) | |
| 299 | 339 | continue | |
| 340 | + module = import_module(mod_name) | ||
| 341 | + for item_name in list(dir_result): | ||
| 342 | + item = getattr(module, item_name) | ||
| 343 | + # an alias such as array.ArrayType is scanned under its real name | ||
| 344 | + if ( | ||
| 345 | + is_native_class(item) | ||
| 346 | + and item not in BUILTIN_TYPES | ||
| 347 | + and item.__name__ == item_name | ||
| 348 | + ): | ||
| 349 | + for attr, info in own_attrs(item): | ||
| 350 | + dir_result[f"{item_name}.{attr}"] = info | ||
| 300 | 351 | modules[mod_name] = dir_result | |
| 301 | 352 | return modules | |
| 302 | 353 | ||
@@ -352,22 +403,34 @@ def method_incompatibility_reason(typ, method_name, real_method_value): | |||
| 352 | 403 | return "" | |
| 353 | 404 | ||
| 354 | 405 | is_inherited = not attr_is_not_inherited(typ, method_name) | |
| 355 | - if is_inherited: | ||
| 406 | + # Inheriting something that reads the same as CPython's own member | ||
| 407 | + # leaves nothing to implement. | ||
| 408 | + if is_inherited and extra_info(getattr(typ, method_name)) != real_method_value: | ||
| 356 | 409 | return "(inherited)" | |
| 357 | 410 | ||
| 358 | - value = extra_info(getattr(typ, method_name)) | ||
| 359 | - if value != real_method_value: | ||
| 360 | - return f"{value} != {real_method_value}" | ||
| 361 | - | ||
| 362 | 411 | return None | |
| 363 | 412 | ||
| 364 | 413 | not_implementeds = {} | |
| 414 | + mismatched_methods = {} | ||
| 415 | + mismatched_method_docs = {} | ||
| 365 | 416 | for name, (typ, real_value, methods) in expected_methods.items(): | |
| 366 | 417 | missing_methods = {} | |
| 367 | 418 | for method, real_method_value in methods: | |
| 368 | 419 | reason = method_incompatibility_reason(typ, method, real_method_value) | |
| 369 | 420 | if reason is not None: | |
| 370 | 421 | missing_methods[method] = reason | |
| 422 | + continue | ||
| 423 | + # A method that exists but differs is a mismatch, not a missing one. | ||
| 424 | + value = extra_info(getattr(typ, method)) | ||
| 425 | + item = f"{name}.{method}" | ||
| 426 | + if value["sig"] != real_method_value["sig"]: | ||
| 427 | + mismatched_methods.setdefault(name, []).append( | ||
| 428 | + (item, value["sig"], real_method_value["sig"]) | ||
| 429 | + ) | ||
| 430 | + if value["doc"] != real_method_value["doc"]: | ||
| 431 | + mismatched_method_docs.setdefault(name, []).append( | ||
| 432 | + (item, value["doc"], real_method_value["doc"]) | ||
| 433 | + ) | ||
| 371 | 434 | if missing_methods: | |
| 372 | 435 | not_implementeds[name] = missing_methods | |
| 373 | 436 | ||
@@ -394,22 +457,44 @@ def method_incompatibility_reason(typ, method_name, real_method_value): | |||
| 394 | 457 | "not_implemented": {}, | |
| 395 | 458 | "failed_to_import": {}, | |
| 396 | 459 | "missing_items": {}, | |
| 397 | - "mismatched_items": {}, | ||
| 398 | - "mismatched_doc_items": {}, | ||
| 460 | + # builtin types first, keyed by type name, then modules | ||
| 461 | + "mismatched_items": mismatched_methods, | ||
| 462 | + "mismatched_doc_items": mismatched_method_docs, | ||
| 399 | 463 | } | |
| 400 | 464 | for modname, cpymod in cpymods.items(): | |
| 401 | 465 | rustpymod = rustpymods.get(modname) | |
| 402 | 466 | if rustpymod is None: | |
| 403 | 467 | result["not_implemented"][modname] = None | |
| 404 | 468 | elif isinstance(rustpymod, Exception): | |
| 405 | - result["failed_to_import"][modname] = rustpymod.__class__.__name__ + str( | ||
| 406 | - rustpymod | ||
| 469 | + result["failed_to_import"][modname] = ( | ||
| 470 | + f"{rustpymod.__class__.__name__}: {rustpymod}" | ||
| 407 | 471 | ) | |
| 408 | 472 | else: | |
| 473 | + module = import_module(modname) | ||
| 474 | + skipped = set() | ||
| 475 | + inherited = set() | ||
| 476 | + for item in cpymod: | ||
| 477 | + cls_name, dot, attr = item.partition(".") | ||
| 478 | + if not dot: | ||
| 479 | + continue | ||
| 480 | + cls = getattr(module, cls_name, None) | ||
| 481 | + if not isinstance(cls, type): | ||
| 482 | + # The class line already reports a missing class or one | ||
| 483 | + # implemented as something else. | ||
| 484 | + skipped.add(item) | ||
| 485 | + elif not hasattr(cls, attr): | ||
| 486 | + continue | ||
| 487 | + elif attr_is_not_inherited(cls, attr): | ||
| 488 | + rustpymod[item] = extra_info(getattr(cls, attr)) | ||
| 489 | + elif extra_info(getattr(cls, attr)) == cpymod[item]: | ||
| 490 | + rustpymod[item] = cpymod[item] | ||
| 491 | + else: | ||
| 492 | + inherited.add(item) | ||
| 409 | 493 | implemented_items = sorted(set(cpymod) & set(rustpymod)) | |
| 410 | - mod_missing_items = set(cpymod) - set(rustpymod) | ||
| 494 | + mod_missing_items = set(cpymod) - set(rustpymod) - skipped | ||
| 411 | 495 | mod_missing_items = sorted( | |
| 412 | - f"{modname}.{item}" for item in mod_missing_items | ||
| 496 | + f"{modname}.{item}" + (" (inherited)" if item in inherited else "") | ||
| 497 | + for item in mod_missing_items | ||
| 413 | 498 | ) | |
| 414 | 499 | mod_mismatched_items = [ | |
| 415 | 500 | (f"{modname}.{item}", rustpymod[item]["sig"], cpymod[item]["sig"]) | |
@@ -422,14 +507,13 @@ def method_incompatibility_reason(typ, method_name, real_method_value): | |||
| 422 | 507 | for item in implemented_items | |
| 423 | 508 | if rustpymod[item]["doc"] != cpymod[item]["doc"] | |
| 424 | 509 | ] | |
| 425 | - if mod_missing_items or mod_mismatched_items: | ||
| 426 | - if mod_missing_items: | ||
| 427 | - result["missing_items"][modname] = mod_missing_items | ||
| 428 | - if mod_mismatched_items: | ||
| 429 | - result["mismatched_items"][modname] = mod_mismatched_items | ||
| 430 | - if mod_mismatched_doc_items: | ||
| 431 | - result["mismatched_doc_items"][modname] = mod_mismatched_doc_items | ||
| 432 | - else: | ||
| 510 | + if mod_missing_items: | ||
| 511 | + result["missing_items"][modname] = mod_missing_items | ||
| 512 | + if mod_mismatched_items: | ||
| 513 | + result["mismatched_items"][modname] = mod_mismatched_items | ||
| 514 | + if mod_mismatched_doc_items: | ||
| 515 | + result["mismatched_doc_items"][modname] = mod_mismatched_doc_items | ||
| 516 | + if not (mod_missing_items or mod_mismatched_items): | ||
| 433 | 517 | result["implemented"][modname] = None | |
| 434 | 518 | ||
| 435 | 519 | result["cpython_modules"] = cpymods | |
| Back | FazBrowse Home | New Git URL |
0 commit comments