Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

False negative: override of @final property #9795

Closed
erictraut opened this issue Jan 31, 2025 · 4 comments
Closed

False negative: override of @final property #9795

erictraut opened this issue Jan 31, 2025 · 4 comments
Labels
addressed in next version Issue is fixed and will appear in next published version bug Something isn't working spec compliance

Comments

@erictraut
Copy link
Collaborator

erictraut commented Jan 31, 2025

The typing spec indicates that @final should be enforced for properties, but pyright doesn't emit an error in this case.

from typing import final


class Base:
    @final
    @property
    def prop(self) -> int:
        return 1


class Child(Base):
    # This should result in an error.
    @property
    def prop(self) -> int:
        return 2

Refer to this thread.

@Jimmybentley579
Copy link

from abc import ABCMeta

class FinalPropertyMeta(ABCMeta):
def new(mcs, name, bases, namespace):
cls = super().new(mcs, name, bases, namespace)
if 'final_properties' not in cls.dict:
cls.final_properties = set()

    # Collect all final properties from current class definition
    cls.__final_properties__.update(
        attr_name 
        for attr_name in namespace 
        if isinstance(getattr(cls.__dict__[attr_name], 'fget', None), property)
           and getattr(namespace[attr_name].fget._is_final_property_, False)
    )
    
    # Check if any base class has defined these properties as final
    for base_class in bases:
        if hasattr(base_class.__dict__.get('__metaclass__', {}), '__bases__'):
            base_final_properties = getattr(base_class.__dict__, '__final_properties__', set())
            intersections = base_final_properties.intersection(cls.__dict__)
            if intersections:
                raise TypeError(f"Cannot override final properties {intersections} in {name}")

    return cls

def _is_final_property(func):
func.is_final_property = True
return func

class FinalProperty(metaclass=FinalPropertyMeta):
pass

class Base(FinalProperty):
@Property @is_final_property
def prop(self) -> int:
return 1

class Child(Base):
# This will raise a TypeError at runtime due to metaclass check.
@Property
def prop(self) -> int:
return 2

@erictraut
Copy link
Collaborator Author

The spec is currently unclear on whether the @final applies to the attribute (which is a descriptor object) or the method (which is wrapped by the descriptor object). This matters because if it applies to the attribute, then it's not possible to define a setter or a deleter on the property. If it applies to the method, then it's not clear what it means for override behaviors if, for example, the getter is marked @final but the setter is not. I'd prefer to have these details ironed out in the spec before I implement this because the implementation will differ significantly between these different interpretations.

@Jimmybentley579
Copy link

Thank you. I'm not proceeding at this time on this project.

erictraut added a commit that referenced this issue Feb 9, 2025
erictraut added a commit that referenced this issue Feb 9, 2025
@erictraut erictraut added the addressed in next version Issue is fixed and will appear in next published version label Feb 9, 2025
@erictraut
Copy link
Collaborator Author

This is addressed in pyright 1.1.394.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addressed in next version Issue is fixed and will appear in next published version bug Something isn't working spec compliance
Projects
None yet
Development

No branches or pull requests

2 participants