Skip to content

Navigation Menu

Sign in
Sign up

Attribute store through a Protocol-typed receiver bypasses the setter check and panics in pycc_mir #958

Open
Milestone

Description

Summary

An attribute store through a Protocol-typed receiver is not checked against the conforming class's write surface. When the conforming class satisfies the protocol's attribute member with a read-only @property, pycc_types::check accepts the program and pycc_mir panics with an internal error instead of emitting a diagnostic.

Found by the iteration-18 adversarial advisor round while sizing #914 (a class attribute satisfying a protocol attribute member); #914's fix would add a second route into the same hole (a read-only class attribute reached through a protocol-typed receiver), so this defect is recorded separately rather than folded into #914.

Reproduction (observed on main = 73b15320, debug build, 2026年09月06日)

from typing import Protocol
class HasLimit(Protocol):
 limit: int
class Impl:
 @property
 def limit(self) -> int:
 return 1
def bump(p: HasLimit) -> None:
 p.limit = 2
i = Impl()
bump(i)
print(i.limit)

pycc build repro.py:

thread 'main' panicked at crates/pycc_mir/src/stmt.rs:582:25:
pycc_mir: internal error: property `limit` on class `Impl` has no setter -- pycc_types::check should have rejected this assignment before it reached pycc_mir

Expected: a compile-time diagnostic. CPython raises AttributeError: property 'limit' of 'Impl' object has no setter at runtime; pycc's convention (a wrong program is rejected at check time) points at a T0044-family rejection at the p.limit = 2 site after monomorphization, or at the call site (bump(i)) if the check is kept on the conformance side.

The same program with an instance attribute (self.limit = 1 in __init__) compiles and prints 2, which is correct and must keep working.

Root cause (as read on 73b15320)

  • check_attr_set (crates/pycc_types/src/class.rs:768) gates its class-attribute rejection and its property-setter walk on Ty::Instance; a Ty::Protocol base falls through to resolve_attr_get, which only answers "does the member exist with this type".
  • monomorphize_protocol_params (crates/pycc_types/src/monomorphize.rs, D-166) specializes the function body for the concrete class but performs no re-check of attribute stores against the concrete class's property/class-attribute tables.

Scope

  • Decide where the write check lives: on the protocol side (a protocol attribute member is read-write; a class whose member is a setter-less property or, after A class attribute should satisfy a Protocol attribute member #914 , a class attribute does not conform when the protocol member is written through -- mypy/pyright do not track this) or on the monomorphized body (re-run check_attr_set against the concrete receiver class after specialization). Record the choice in docs/TYPE_SYSTEM.md.
  • Cover: setter-less property (panic above), property with setter (must keep working), instance attribute (must keep working), and -- once A class attribute should satisfy a Protocol attribute member #914 lands -- read-only class attribute.
  • 100% line and region coverage (D-014).

References

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    AltStyle によって変換されたページ (->オリジナル) /