Report illegal zero-argument super() in nested functions and lambdas - #11643
Report illegal zero-argument super() in nested functions and lambdas#11643Henry Su (hsusul) wants to merge 2 commits into
Conversation
CPython raises RuntimeError because those scopes do not receive the compiler-inserted __class__ cell; list comprehensions in a method remain valid.
|
🔒 Automated review in progress — Rich Chiodo (@rchiodo) is auto-reviewing this PR. |
| parent = parent.parent; | ||
| } | ||
|
|
||
| return false; |
There was a problem hiding this comment.
Issue · Please address or respond
A nested callable can validly use zero-argument super() when its own first argument is the instance and it closes over __class__ (for example, def inner(self): return super()). Rejecting every nested function or lambda creates a false diagnostic; classify this based on the executing frame's arguments and __class__ cell rather than nesting alone.
| parent = parent.parent; | ||
| } | ||
|
|
||
| return false; |
There was a problem hiding this comment.
Issue · Please address or respond
Defaults of a nested callable are evaluated in the enclosing method frame. For example, def inner(value=super()): ... can validly use the enclosing method's instance and __class__ cell, but this returns false as soon as it reaches inner. Handle parameter defaults as expressions evaluated in the enclosing scope.
|
|
||
| return false; | ||
| } | ||
|
|
There was a problem hiding this comment.
Issue · Please address or respond
This accepts all comprehensions, but generator expressions retain a separate frame whose implicit iterator argument is not the method instance. List, set, and dict comprehensions likewise had separate frames before Python 3.12. Distinguish comprehension kinds and the configured Python version so runtime-failing forms receive the diagnostic.
|
|
||
| return false; | ||
| } | ||
|
|
There was a problem hiding this comment.
Warning · Non-blocking recommendation
This duplicates evaluation-scope boundary handling already provided by getEvaluationScopeNode, including decorators, defaults, class headers, lambdas, and comprehensions. Derive this predicate from that machinery with only the needed frame-specific logic so the two scope definitions do not drift.
| const analysisResults = TestUtils.typeAnalyzeSampleFiles(['super1.py']); | ||
|
|
||
| TestUtils.validateResults(analysisResults, 6); | ||
| TestUtils.validateResults(analysisResults, 8); |
There was a problem hiding this comment.
Info · Optional note
The aggregate diagnostic count cannot show that errors are reported on the nested calls and remain absent from valid expressions. Add focused assertions that distinguish each positive and negative scenario.
| # of super is illegal in a lambda. | ||
| lambda: super().method1() | ||
|
|
||
| # Comprehensions execute in the enclosing method and are valid. |
There was a problem hiding this comment.
Info · Optional note
The described behavior includes nested async def, but this regression sample covers only def and lambda. Add an async def case to protect that behavior.
| if (curNode.d.decorators.some((decorator) => decorator === prevNode)) { | ||
| prevNode = curNode; | ||
| curNode = curNode.parent; | ||
| continue; |
There was a problem hiding this comment.
Issue · Please address or respond
Can this preserve nested callables that have an explicit first parameter? super() uses the current frame's first argument plus the inherited __class__ cell, so a nested def inner(obj): return super() invoked as inner(self) can succeed. Treating every nested function or lambda as illegal produces a false diagnostic for this valid form; please model that case and add coverage.
| } | ||
|
|
||
| let parent: ParseNode | undefined = curNode.parent; | ||
| while (parent) { |
There was a problem hiding this comment.
Issue · Please address or respond
Does this distinguish a nested function's default-value evaluation from its body? A default such as def inner(value=super()): ... declared inside a method is evaluated in the enclosing method's frame, but this walk reaches inner and returns false. Please classify parameter defaults by their enclosing evaluation scope so this valid use is not diagnosed.
Derive the check from getEvaluationScopeNode so decorators, parameter default values, class headers, lambdas, and comprehensions share one definition of scope boundaries. A nested function or lambda is now allowed when it declares a first positional parameter, since super() uses that frame's first argument plus the inherited __class__ cell. Generator expressions always execute in their own frame, and list, set, and dict comprehensions did so before Python 3.12, so those forms are diagnosed based on the configured Python version.
| const analysisResults = TestUtils.typeAnalyzeSampleFiles(['super1.py']); | ||
|
|
||
| TestUtils.validateResults(analysisResults, 6); | ||
| TestUtils.validateResults(analysisResults, 10); |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
📍 packages/pyright-internal/src/tests/typeEvaluator2.test.ts:183
The prior test-precision feedback remains unresolved: an aggregate count can pass if a missing diagnostic is offset by one on a valid expression. Add range-specific assertions, or split valid and invalid scenarios into separately analyzed samples, so each new case is independently protected.
[verified]
Rich Chiodo (rchiodo)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
Heejae Chang (heejaechang)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
Summary
super()is valid only in a method (and in comprehensions that run in that method). Nesteddef/async defand lambdas raiseRuntimeError: super(): no argumentsbecause they do not get the compiler-inserted__class__cell.super()inside a nested function as if it were in the enclosing method (super1.pyeven contained this case with no expected error).superCallZeroArgFormdiagnostic. Nested class bases evaluated in a method remain allowed; nested class bodies still error.Test plan
npx jest typeEvaluator2.test.ts -t Super --forceExit(frompackages/pyright-internal)def/lambdaraise;[super() for _ in [1]]inside a method succeedssuper().method()in an editor shows the zero-argument diagnostic