Repository: google/pytype

Issue:
Update README.md File
The README.md is updated.


Discussion:

Resolving patch:
diff --git a/pytype/attribute.py b/pytype/attribute.py
--- a/pytype/attribute.py
+++ b/pytype/attribute.py
@@ -274,19 +274,25 @@ def _get_attribute(self, node, obj, cls, name, valself):
             node, obj, name, valself, skip=())
       else:
         node, attr = self._get_member(node, obj, name, valself)
-    if attr is None and obj.maybe_missing_members:
-      # The VM hit maximum depth while initializing this instance, so it may
-      # have attributes that we don't know about. These attributes take
-      # precedence over class attributes and __getattr__, so we set `attr` to
-      # Any immediately.
-      attr = self.ctx.new_unsolvable(node)
+    # If the VM hit maximum depth while initializing this instance, it may have
+    # attributes that we don't know about.
+    is_unknown_instance_attribute = attr is None and obj.maybe_missing_members
     if attr is None and cls:
       # Check for the attribute on the class.
       node, attr = self.get_attribute(node, cls, name, valself)
-      if attr is None:
+      if attr:
+        # If the attribute is a method, then we allow it to take precedence over
+        # the possible unknown instance attribute, since otherwise method lookup
+        # on classes with _HAS_DYNAMIC_ATTRIBUTES would always return Any.
+        if (is_unknown_instance_attribute and
+            any(isinstance(v, abstract.FUNCTION_TYPES) for v in attr.data)):
+          is_unknown_instance_attribute = False
+      elif not is_unknown_instance_attribute:
         # Fall back to __getattr__ if the attribute doesn't otherwise exist.
         node, attr = self._get_attribute_computed(
             node, cls, name, valself, compute_function="__getattr__")
+    if is_unknown_instance_attribute:
+      attr = self.ctx.new_unsolvable(node)
     if attr is not None:
       attr = self._filter_var(node, attr)
     return node, attr
diff --git a/pytype/convert.py b/pytype/convert.py
--- a/pytype/convert.py
+++ b/pytype/convert.py
@@ -2,6 +2,7 @@
 
 import logging
 import types
+from typing import Any, Dict
 
 from pytype import blocks
 from pytype import datatypes
@@ -65,7 +66,7 @@ def __init__(self, ctx):
     super().__init__(ctx)
     ctx.convert = self  # to make constant_to_value calls below work
 
-    self._convert_cache = {}
+    self._convert_cache: Dict[Any, Any] = {}
     self._resolved_late_types = {}  # performance cache
 
     # Initialize primitive_classes to empty to allow constant_to_value to run.
@@ -303,7 +304,7 @@ def get_maybe_abstract_instance(self, data):
         return self.primitive_class_instances[data_type]
     return data
 
-  def _create_new_unknown_value(self, action):
+  def _create_new_unknown_value(self, action) -> abstract.Unknown:
     if not action or not self.ctx.vm.frame:
       return abstract.Unknown(self.ctx)
     # We allow only one Unknown at each point in the program, regardless of


Test patch:
diff --git a/pytype/tests/test_methods2.py b/pytype/tests/test_methods2.py
--- a/pytype/tests/test_methods2.py
+++ b/pytype/tests/test_methods2.py
@@ -115,6 +115,16 @@ def f(x: Union[Foo, Bar]):
           return x()
     """)
 
+  def test_lookup_on_dynamic_class(self):
+    self.Check("""
+      class Foo:
+        _HAS_DYNAMIC_ATTRIBUTES = True
+        def f(self) -> str:
+          return ''
+        def g(self):
+          assert_type(self.f(), str)
+    """)
+
 
 class TestMethodsPy3(test_base.BaseTest):
   """Test python3-specific method features."""