From 78ee166be64f635912704da908b7a3e8c751205c Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 16 Jan 2023 11:03:49 -0800 Subject: [PATCH] Do not call `.mro()` method on non-types (#587) --- docs/changelog.md | 1 + pyanalyze/attributes.py | 101 ++++++++++++++++++++-------------------- 2 files changed, 52 insertions(+), 50 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 07097215..6244b169 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,7 @@ ## Unreleased +- Do not call `.mro()` method on non-types (#587) - Add `class_attribute_transformers` hook (#585) - Support for PEP 702 (`@typing.deprecated`) (#578) - Simplify import handling; stop trying to import modules at type checking time (#566) diff --git a/pyanalyze/attributes.py b/pyanalyze/attributes.py index efb43217..90ff5a73 100644 --- a/pyanalyze/attributes.py +++ b/pyanalyze/attributes.py @@ -471,61 +471,62 @@ def _get_attribute_from_mro( if attr_type is not None: return (attr_type, typ, False) - try: - mro = list(typ.mro()) - except Exception: - # broken mro method - pass - else: - for base_cls in mro: - if ctx.skip_mro and base_cls is not typ: - continue - - typeshed_type = ctx.get_attribute_from_typeshed( - base_cls, on_class=on_class or ctx.skip_unwrap - ) - if typeshed_type is not UNINITIALIZED_VALUE: - if ctx.prefer_typeshed: - return typeshed_type, base_cls, False - # If it's a callable, we'll probably do better - # getting the attribute from the type ourselves, - # because we may have our own implementation. - if not isinstance(typeshed_type, CallableValue): - return typeshed_type, base_cls, False + if safe_isinstance(typ, type): + try: + mro = list(type.mro(typ)) + except Exception: + # broken mro method + pass + else: + for base_cls in mro: + if ctx.skip_mro and base_cls is not typ: + continue - try: - base_dict = base_cls.__dict__ - except Exception: - continue - - try: - # Make sure to use only __annotations__ that are actually on this - # class, not ones inherited from a base class. - annotations = base_dict["__annotations__"] - except Exception: - pass - else: - attr_type = type_from_annotations( - annotations, ctx.attr, ctx=AnnotationsContext(ctx, base_cls) + typeshed_type = ctx.get_attribute_from_typeshed( + base_cls, on_class=on_class or ctx.skip_unwrap ) - if attr_type is not None: - return (attr_type, base_cls, False) - - try: - # Make sure we use only the object from this class, but do invoke - # the descriptor protocol with getattr. - base_dict[ctx.attr] - except Exception: - pass - else: + if typeshed_type is not UNINITIALIZED_VALUE: + if ctx.prefer_typeshed: + return typeshed_type, base_cls, False + # If it's a callable, we'll probably do better + # getting the attribute from the type ourselves, + # because we may have our own implementation. + if not isinstance(typeshed_type, CallableValue): + return typeshed_type, base_cls, False + + try: + base_dict = base_cls.__dict__ + except Exception: + continue + try: - val = KnownValue(getattr(typ, ctx.attr)) + # Make sure to use only __annotations__ that are actually on this + # class, not ones inherited from a base class. + annotations = base_dict["__annotations__"] except Exception: - val = AnyValue(AnySource.inference) - return val, base_cls, True + pass + else: + attr_type = type_from_annotations( + annotations, ctx.attr, ctx=AnnotationsContext(ctx, base_cls) + ) + if attr_type is not None: + return (attr_type, base_cls, False) - if typeshed_type is not UNINITIALIZED_VALUE: - return typeshed_type, base_cls, False + try: + # Make sure we use only the object from this class, but do invoke + # the descriptor protocol with getattr. + base_dict[ctx.attr] + except Exception: + pass + else: + try: + val = KnownValue(getattr(typ, ctx.attr)) + except Exception: + val = AnyValue(AnySource.inference) + return val, base_cls, True + + if typeshed_type is not UNINITIALIZED_VALUE: + return typeshed_type, base_cls, False attrs_type = get_attrs_attribute(typ, ctx) if attrs_type is not None: