Nimblesite / Nimblesite/Basilisk

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

Open
#381 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

high-priority
Dominant language
Rust
Stars
54
Forks
3
PR merge metrics
No merged PRs in 30d

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the constructors_call_init rule and reproduce the issue with the minimal C(1).class and C(1).g() examples in the CLI or playground. Trace why the rule handles bare calls and assignment RHS but not calls used as attribute receivers, then check the other contexts listed in the issue. Done when those cases report the constructor arity error without regressing the controls.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, rust
Domain
devtools
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.