Skip to content

constructors_call_init only fires when the constructor call is the outermost expression: C(1) caught, C(1).m() silent #381

Description

@MelbourneDeveloper

Summary

constructors_call_init fires only when the constructor call is the outermost expression of a statement (or the RHS of an assignment). The moment the result is used — C(1).attr, C(1).method() — the arity check is skipped.

Found via the "type torture" puzzle on X: https://x.com/charliermarsh/status/2082885991686090885

Reproduced on a clean cargo build --release of main @ 5b756d3; identical on CLI and playground.

Minimal reproduction

class C: ...
C(1)              # line 2 — CAUGHT
C(1).__class__    # line 3 — SILENT
x = C(1)          # line 4 — CAUGHT

Output:

error[constructors_call_init]: Class `C` does not define `__init__` or `__new__` and inherits only from `object`; constructor does not accept arguments
  --> k1.py:2:1
error[constructors_call_init]: Class `C` does not define `__init__` or `__new__` and inherits only from `object`; constructor does not accept arguments
  --> k1.py:4:5

Line 3 is the same erroneous call and is not reported.

Same shape with a method call:

def f() -> int: return 0
class C:
    g = f
C(1)        # CAUGHT
C(1).g()    # SILENT

Why it matters beyond the toy case

This is what hid the error in the reported torture puzzle. C(C()()).f() should report that C inherits object.__init__ and takes no arguments, but because the constructor call is the receiver of a trailing .f(), the check never runs. Chained construction — Config(path).load(), Session(url).get() — is an extremely common Python idiom, so this is a large real-world hole, not an edge case.

Controls (all CAUGHT, so the class shape is not the trigger)

class C: ...
C(1)          # literal arg
C(x)          # name arg
C(mk())       # call arg
C(C())        # constructor arg
C(C()())      # nested-call arg
class C:
    g = 1       # assigned attribute
class C:
    g: int      # annotated attribute
class C:
    __call__: "C"
class C:
    def m(self) -> None: ...

Every one of the above reports correctly when written as a bare C(1). Only wrapping the call in an attribute access or method call suppresses it.

Expected

Constructor arity is a property of the call expression, not of its syntactic position. The rule should visit every Call node whose callee resolves to a class, wherever that call appears — statement, receiver, argument, subscript base, comprehension element, or return value.

Metadata

Metadata

Assignees

No one assigned

    Labels

    high-priorityHigh priority — schedule ahead of other feature work

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions