Skip to content

Report an error for a default on a method's self/cls parameter#3844

Open
markselby9 wants to merge 1 commit into
facebook:mainfrom
markselby9:feat/3729-self-cls-default
Open

Report an error for a default on a method's self/cls parameter#3844
markselby9 wants to merge 1 commit into
facebook:mainfrom
markselby9:feat/3729-self-cls-default

Conversation

@markselby9

Copy link
Copy Markdown
Contributor

Closes #3729.

Summary

A method's self/cls receiver is always supplied implicitly at call time, so a default value on it (e.g. def m(self=1)) is unreachable and almost always a mistake. Pyrefly already relied on the invariant that the receiver never has a default, yet silently accepted such definitions. basedpyright reports the same via reportSelfClsParameterDefault.

The check runs during function elaboration: a function has an implicit receiver exactly when self_type is Some (instance methods, classmethods including __init_subclass__/__class_getitem__, properties, and __new__; not staticmethods or top-level functions). When such a function's first positional parameter has a default, it emits a bad-function-definition error pointing at the default.

Test Plan

New tests in pyrefly/lib/test/self_cls_default.rs cover instance / classmethod / positional-only / __new__ / __init_subclass__ / property receivers (flagged) and staticmethod, top-level function, and non-receiver params (not flagged). cargo test passes; full lib test suite is green.

A method's `self`/`cls` receiver is always supplied implicitly at call time, so
a default value on it (e.g. `def m(self=1)`) is unreachable and almost always a
mistake. Pyrefly already relied on the invariant that "self/cls never has a
default" elsewhere, yet silently accepted such definitions.

Detect this during function elaboration: a function has an implicit receiver
exactly when `self_type` is `Some` (instance methods, classmethods, `__new__`)
and not for staticmethods or top-level functions. When such a function's first
positional parameter carries a default, emit a `BadFunctionDefinition` error
pointing at the default value.

Closes facebook#3729
@github-actions

Copy link
Copy Markdown

Diff from mypy_primer, showing the effect of this PR on open source code:

alerta (https://github.com/alerta/alerta)
+ ERROR alerta/database/base.py:214:47-51: Parameter `query` is the `self`/`cls` parameter and cannot have a default value [bad-function-definition]

zope.interface (https://github.com/zopefoundation/zope.interface)
+ ERROR src/zope/interface/common/builtins.py:47:18-22: Parameter `key` is the `self`/`cls` parameter and cannot have a default value [bad-function-definition]
+ ERROR src/zope/interface/common/idatetime.py:242:16-20: Parameter `tz` is the `self`/`cls` parameter and cannot have a default value [bad-function-definition]
+ ERROR src/zope/interface/common/idatetime.py:447:23-26: Parameter `sep` is the `self`/`cls` parameter and cannot have a default value [bad-function-definition]
+ ERROR src/zope/interface/common/sequence.py:152:19-21: Parameter `index` is the `self`/`cls` parameter and cannot have a default value [bad-function-definition]
+ ERROR src/zope/interface/common/sequence.py:161:22-26: Parameter `cmpfunc` is the `self`/`cls` parameter and cannot have a default value [bad-function-definition]
+ ERROR src/zope/interface/interfaces.py:222:26-30: Parameter `callback` is the `self`/`cls` parameter and cannot have a default value [bad-function-definition]
+ ERROR src/zope/interface/interfaces.py:353:19-24: Parameter `all` is the `self`/`cls` parameter and cannot have a default value [bad-function-definition]
+ ERROR src/zope/interface/interfaces.py:364:34-39: Parameter `all` is the `self`/`cls` parameter and cannot have a default value [bad-function-definition]
+ ERROR src/zope/interface/interfaces.py:1189:35-39: Parameter `component` is the `self`/`cls` parameter and cannot have a default value [bad-function-definition]
+ ERROR src/zope/interface/interfaces.py:1217:37-41: Parameter `component` is the `self`/`cls` parameter and cannot have a default value [bad-function-definition]
+ ERROR src/zope/interface/interfaces.py:1292:35-39: Parameter `factory` is the `self`/`cls` parameter and cannot have a default value [bad-function-definition]
+ ERROR src/zope/interface/interfaces.py:1379:47-51: Parameter `factory` is the `self`/`cls` parameter and cannot have a default value [bad-function-definition]
+ ERROR src/zope/interface/interfaces.py:1467:35-39: Parameter `handler` is the `self`/`cls` parameter and cannot have a default value [bad-function-definition]

@github-actions

Copy link
Copy Markdown

Primer Diff Classification

✅ 2 improvement(s) | 2 project(s) total | +14 errors

2 improvement(s) across alerta, zope.interface.

Project Verdict Changes Error Kinds Root Cause
alerta ✅ Improvement +1 bad-function-definition is_some()
zope.interface ✅ Improvement +13 self/cls default on zope.interface methods is_some()
Detailed analysis

✅ Improvement (2)

alerta (+1)

This is a genuine bug in the alerta codebase. Line 214 defines def get_environments_count(query: Query = None) -> int: inside class Database, but unlike every other method in the class, it's missing the self parameter. The first parameter query is therefore treated as the implicit receiver. The = None default on it is unreachable since the instance is always passed implicitly. Both mypy and pyright agree this is an error. Pyrefly is correctly catching a real bug — a missing self parameter that was likely an oversight by the developer.
Attribution: The change in pyrefly/lib/alt/function.rs in the AnswersSolver implementation adds a new check: when self_type.[is_some()](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/function.rs) (meaning the function has an implicit receiver), it checks if the first positional parameter has a default value and emits a BadFunctionDefinition error. This is the direct cause of the new error on get_environments_count where query is the first (and only) parameter and has a default value None.

zope.interface (+13)

self/cls default on zope.interface methods: All 13 errors are false positives on zope.interface's convention of omitting self in interface method definitions. The first parameter (key, tz, sep, etc.) is not actually self/cls — it's a real parameter. However, pyright also flags all 13, confirming this is a shared type checker limitation rather than a pyrefly-specific bug. The check is correct for normal classes and catches real bugs like def m(self=1).

Overall: These are false positives on zope.interface's idiomatic pattern where interface methods deliberately omit self and the first parameter is a real parameter with a default. However, since pyright flags all 13 of these too (and mypy flags 2), this is a known and accepted limitation of static type checkers that don't understand zope.interface's metaclass. The check itself is correct for normal Python classes. Given that pyright agrees on all 13 errors, this is consistent behavior rather than a pyrefly-specific regression. The new check catches real bugs in normal code (e.g., def m(self=1)) and the false positives here are on an unusual framework pattern. On balance, this is an improvement — the check is correct in general, and the zope.interface false positives are a shared limitation across type checkers.

Attribution: The new check in pyrefly/lib/alt/function.rs at the block starting with if self_type.[is_some()](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/function.rs) detects when the first positional parameter of a method has a default value. Since pyrefly treats zope.interface classes as regular classes (not understanding the Interface metaclass), it incorrectly identifies parameters like key, tz, and sep as the self/cls receiver.


Was this helpful? React with 👍 or 👎

Classification by primer-classifier (2 LLM)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

report an error when assigning a default value to self or cls

2 participants