Skip to content

Final variable is mutable when using structural subtyping #10850

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

Closed
Tomatosoup97 opened this issue Jul 21, 2021 · 1 comment · Fixed by #13513
Closed

Final variable is mutable when using structural subtyping #10850

Tomatosoup97 opened this issue Jul 21, 2021 · 1 comment · Fixed by #13513
Labels
bug mypy got something wrong topic-final PEP 591 topic-protocols

Comments

@Tomatosoup97
Copy link

Tomatosoup97 commented Jul 21, 2021

Bug Report

Under structural subtyping with a variable narrowed to Final, it's possible to modify the var contents

To Reproduce

# poc.py
from dataclasses import dataclass
from typing import Protocol, Final

@dataclass
class FinalY:
    y: Final[int] = 0

class Y(Protocol):
    y: int

def f(x: Y) -> Y:
    x.y = 42
    return x

print(f(FinalY(17)))

$ mypy poc.py
Success: no issues found in 1 source file

$ python poc.py
FinalY(y=42)

Expected Behavior

Class with a variable Final[T] should not be a structural subtype of a protocol with variable T

Your Environment

  • Mypy version used: 0.910
  • Python version used: 3.9.6

This bug was inspired by @erictraut's example of how to deal with other kind of issue

@KotlinIsland
Copy link
Contributor

KotlinIsland commented Jul 14, 2022

@JelleZijlstra This is unrelated to topic-dataclasses

Here is a minified example of the issue:

from typing import Protocol, Final

class MutY(Protocol):
    y: int

class Y:
    y: Final = 0

y: MutY = Y()
y.y = 1

playground

An error is correctly shown when you attempt to subtype the protocol directly:

from typing import Protocol, Final


class MutY(Protocol):
    y: int

class Y(MutY):
    y: Final = 0  # error: Cannot override writable attribute "y" with a final one
    
y: MutY = Y()
y.y = 1

ilevkivskyi added a commit that referenced this issue Aug 27, 2022
Fixes #5018 
Fixes #5439
Fixes #10850 

The implementation is simple but not the most beautiful one. I simply add a new slot to the `Instance` class that represents content of the module. This new attribute is short lived (it is not serialized, and not even stored on variables etc., because we erase it in `copy_modified()`). We don't need to store it, because all the information we need is already available in `MypyFile` node. We just need the new attribute to communicate between the checker and `subtypes.py`.

Other possible alternatives like introducing new dedicated `ModuleType`, or passing the symbol tables to `subtypes.py` both look way to complicated. Another argument in favor of this new slot is it could be useful for other things, like `hasattr()` support and ad hoc callable attributes (btw I am already working on the former).

Note there is one important limitation: since we don't store the module information, we can't support module objects stored in nested positions, like `self.mods = (foo, bar)` and then `accepts_protocol(self.mods[0])`. We only support variables (name expressions) and direct instance, class, or module attributes (see tests). I think this will cover 99% of possible use-cases.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-final PEP 591 topic-protocols
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants