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

Invariant on a class changes the type of the class #224

Closed
ciresnave opened this issue Aug 11, 2021 · 10 comments
Closed

Invariant on a class changes the type of the class #224

ciresnave opened this issue Aug 11, 2021 · 10 comments

Comments

@ciresnave
Copy link

ciresnave commented Aug 11, 2021

from icontract import invariant

class TestClass1():
    test_int: int = 1

reveal_type(TestClass1)
# Type[TestClass1]

@invariant(lambda self: self.test_int == 1)
class TestClass2():
    test_int: int = 1

reveal_type(TestClass2)
# type
@ciresnave
Copy link
Author

ciresnave commented Aug 11, 2021

This was originally brought to my attention by PyLance which didn't like me using the invariant wrapped class as a type hint for a field in a dataclass like so:

@dataclass
class TestClass3():
    test_class: TestClass2

PyLance complained that it expected a Class but received a type instead.

@ciresnave
Copy link
Author

I should mention that although this is a discrepancy, the code still works. It's just PyLance and other type checkers that may complain about the difference.

mristin added a commit that referenced this issue Aug 12, 2021
This patch upgrades the mypy to 0.910. This was necessary so that we can
test the type inference with the latest mypy.

Related to #224.
mristin added a commit that referenced this issue Aug 12, 2021
This patch upgrades the mypy to 0.910. This was necessary so that we can
test the type inference with the latest mypy.

We also had to upgrade pylint 2.9.6 since the checks broke on CI due to
astroid having hard time parsing `_metaclass.py`. Due to this upgrade,
we had to disable all `no-member` warnings from pylint as pylint did not
properly infer types in condition functions.

Related to #224.
mristin added a commit that referenced this issue Aug 12, 2021
This patch upgrades the mypy to 0.910. This was necessary so that we can
test the type inference with the latest mypy.

We also had to upgrade pylint 2.9.6 since the checks broke on CI due to
astroid having hard time parsing `_metaclass.py`. Due to this upgrade,
we had to disable all `no-member` warnings from pylint as pylint did not
properly infer types in condition functions.

Related to #224.
mristin added a commit that referenced this issue Aug 12, 2021
This patch adds a test case to make sure that class invariants do not
break the type inference with mypy.

Related to #224.
mristin added a commit that referenced this issue Aug 12, 2021
This patch adds a test case to make sure that class invariants do not
break the type inference with mypy.

Related to #224.
@mristin
Copy link
Collaborator

mristin commented Aug 12, 2021

Hi @ciresnave ,
Thanks a lot for reporting this issue! This is very relevant as many people who use contracts also use type checkers.

I have just added a test with mypy (please see this snippet in the pull request 226). The output of mypy 0.910 looks correct to me. Could you please double-check?

Does your version of PyLance uses mypy or it has its own implementation of type inference? If you happen to use mypy, could we first try to reproduce and fix the issue for your version of mypy and then move on to PyLance afterwards?

@ciresnave
Copy link
Author

Interesting. Mypy does indeed show the two as identical...but it doesn't show the type as such. Instead, it shows the following:

invariant_type_change.py:9:13: note: Revealed type is "def () -> stock_trader.invariant_type_change.TestClass1"
invariant_type_change.py:18:13: note: Revealed type is "def () -> stock_trader.invariant_type_change.TestClass2"

Just out of curiosity I added code to print the types:

print(type(instance_of_TestClass1))
print(type(instance_of_TestClass2))

The results would appear to indicate this may be a PyLance issue:

<class '__main__.TestClass1'>
<class '__main__.TestClass2'>

Maybe this is an issue with PyLance. Thoughts on anything else you would like me to check before I open an issue with PyLance?

mristin added a commit that referenced this issue Aug 12, 2021
The current type annotation of the `invariant` decorator did not use
generics. This patch makes it more specific so that a generic
``TypeVar`` bounded by ``type`` is used in order to help downstream
tools work better.

Related to #224.
@mristin
Copy link
Collaborator

mristin commented Aug 12, 2021

@ciresnave thanks for the further investigation!

The current type annotations for invariant decorator are actually quite vague, so maybe PyLance is having a problem with that. I tried to make a fix in the pull request #227. Could you please check out bb14953 and see if the more precise type annotations work with PyLance?

@ciresnave
Copy link
Author

Okay. I will check that and get you the results ASAP. Since I still feel this is also questionable behavior in PyLance, I have opened an issue with them as well. In case you're curious what they find:

microsoft/pylance-release#1667

@ciresnave
Copy link
Author

It appears your pull request #227 did indeed solve the problem. Interesting. PyLance now sees TestClass2 as Type[TestClass2] as it should.

@ciresnave
Copy link
Author

It also appears this is a known issue in mypy (python/mypy#3135) and has been for years. Basically, mypy isn't checking some aspects of wrapped/decorated classes. PyLance appears to be adhering more closely to the standard than mypy if I'm reading it right.

@mristin
Copy link
Collaborator

mristin commented Aug 13, 2021

@ciresnave thanks for your feedback! I'll merge the PR and bump a patch version this Monday.

mristin added a commit that referenced this issue Aug 16, 2021
The current type annotation of the `invariant` decorator did not use
generics. This patch makes it more specific so that a generic
``TypeVar`` bounded by ``type`` is used in order to help downstream
tools work better.

Related to #224.
@mristin
Copy link
Collaborator

mristin commented Aug 16, 2021

@ciresnave I've just released the patch version on PyPI which should have the issue fixed. Please feel free to re-open the issue if there are further problems.

@mristin mristin closed this as completed Aug 16, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants