Skip to content

Commit 4e76868

Browse files
committed
Add feature flag to bind 'self' correctly in decorated methods.
* Improves get_undecorated_method to not pointlessly create variables. * Falls back to analyzing the undecorated method when a method in a class has a non-transparent decorator. PiperOrigin-RevId: 577253954
1 parent 59e0b01 commit 4e76868

File tree

5 files changed

+27
-3
lines changed

5 files changed

+27
-3
lines changed

pytype/abstract/_classes.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,12 @@ def has_protocol_base(self):
308308
return True
309309
return False
310310

311-
def get_undecorated_method(self, name, node):
311+
def get_undecorated_method(
312+
self, name: str, node: cfg.CFGNode) -> Optional[cfg.Variable]:
313+
if name not in self._undecorated_methods:
314+
return None
312315
return self.ctx.program.NewVariable(
313-
self._undecorated_methods.get(name, ()), (), node)
316+
self._undecorated_methods[name], (), node)
314317

315318

316319
class PyTDClass(

pytype/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ def add_options(o, arglist):
234234

235235

236236
FEATURE_FLAGS = [
237+
_flag("--bind-decorated-methods", False,
238+
"Bind 'self' in methods with non-transparent decorators."),
237239
_flag("--overriding-renamed-parameter-count-checks", False,
238240
"Enable parameter count checks for overriding methods with "
239241
"renamed arguments."),

pytype/tests/test_base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ def setUp(self):
8080
super().setUp()
8181
self.options = config.Options.create(
8282
python_version=self.python_version,
83+
bind_decorated_methods=True,
8384
overriding_renamed_parameter_count_checks=True,
8485
strict_parameter_checks=True,
8586
strict_undefined_checks=True,

pytype/tests/test_decorators2.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,17 @@ def f(self, x: T):
344344
pass
345345
""")
346346

347+
def test_self_in_decorated_method(self):
348+
self.Check("""
349+
from typing import Any
350+
def decorate(f) -> Any:
351+
return f
352+
class C:
353+
@decorate
354+
def f(self):
355+
assert_type(self, C)
356+
""")
357+
347358

348359
if __name__ == "__main__":
349360
test_base.main()

pytype/tracer_vm.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ def bind(cur_node, m):
407407
# which can happen if the method is decorated, for example - then we look up
408408
# the method before any decorators were applied and use that instead.
409409
undecorated_method = cls.get_undecorated_method(method_name, node)
410-
if undecorated_method.data:
410+
if undecorated_method:
411411
return node, bind(node, undecorated_method)
412412
else:
413413
return node, bound_method
@@ -477,6 +477,13 @@ def analyze_class(self, node, val):
477477
name = unwrapped.data[0].name if unwrapped else v.name
478478
self.ctx.errorlog.ignored_abstractmethod(
479479
self.ctx.vm.simple_stack(cls.get_first_opcode()), cls.name, name)
480+
is_method_or_nested_class = any(
481+
isinstance(m, (abstract.FUNCTION_TYPES, abstract.InterpreterClass))
482+
for m in methodvar.data)
483+
if (self.ctx.options.bind_decorated_methods and
484+
not is_method_or_nested_class and
485+
(undecorated_method := cls.get_undecorated_method(name, node))):
486+
methodvar = undecorated_method
480487
b = self._bind_method(node, methodvar, instance)
481488
node = self.analyze_method_var(node, name, b, val)
482489
return node

0 commit comments

Comments
 (0)