Skip to content

Wrapping class with icontract invariant decorator causes PyLance to incorrectly detect type. #1667

@ciresnave

Description

@ciresnave

Environment data

  • Language Server version: 2021.8.2-pre.1
  • OS and version: win32 x64
  • Python version (and distribution if applicable, e.g. Anaconda): Python 3.9.6
  • python.analysis.indexing: undefined
  • python.analysis.typeCheckingMode: strict

Expected behaviour

When using icontract to add an invariant to a class, both mypy and Python's own type() command show the type of a class wrapped by icontract to be the same as one that was never wrapped. PyLance shouldn't have an issue with using a wrapped class in place of the bare class for type hinting.

Actual behaviour

TestClass1 is a bare class.
TestClass2 is a wrapped class.
(see below for the actual code)

PyLance states that TestClass1 is of type "Type[TestClass1]" while it shows that TestClass2 is of type "type".
PyLance states that TestClass2 can't be used to type annotate a dataclass field because it expected a Class but got a type instead. Further, because of that issue, PyLance states that variables within TestClass2 are of unknown type and can't be referenced. The code works fine.

Mypy states:

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

..and printing the type of each from Python shows:

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

Logs

Python Language Server Log

Background analysis message: getDiagnosticsForRange
Background analysis message: getDiagnosticsForRange
Background analysis message: getDiagnosticsForRange
Background analysis message: getDiagnosticsForRange
Background analysis message: markFilesDirty
Background analysis message: markFilesDirty
Background analysis message: analyze
Background analysis message: markFilesDirty
Background analysis message: analyze
Background analysis message: getDiagnosticsForRange
Background analysis message: getDiagnosticsForRange
Background analysis message: markFilesDirty
Background analysis message: invalidateAndForceReanalysis
Background analysis message: invalidateAndForceReanalysis
Background analysis message: markFilesDirty
[Info  - 12:45:04 PM] Searching for source files
[Info  - 12:45:04 PM] Auto-excluding c:\Users\cires\Documents\GitHub\stock_trader\.venv
[Info  - 12:45:04 PM] Found 9 source files
Background analysis message: setTrackedFiles
Background analysis message: markAllFilesDirty
Background analysis message: analyze
[BG(1)] analyzing: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\invariant_type_change.py ...
[BG(1)]   parsing: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\invariant_type_change.py (142ms)
[BG(1)]   parsing: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\builtins.pyi [fs read 0ms] (23ms)
[BG(1)]   binding: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\builtins.pyi (19ms)
[BG(1)]   binding: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\invariant_type_change.py (0ms)
[BG(1)]   checking: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\invariant_type_change.py ...
[BG(1)]     parsing: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\dataclasses.pyi [fs read 0ms] (4ms)
[BG(1)]     binding: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\dataclasses.pyi (1ms)
[BG(1)]   checking: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\invariant_type_change.py (5ms)
[BG(1)] analyzing: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\invariant_type_change.py (189ms)
[BG(1)] parsing: c:\Users\cires\Documents\GitHub\stock_trader\noxfile.py [fs read 1ms] (7ms)
[BG(1)] binding: c:\Users\cires\Documents\GitHub\stock_trader\noxfile.py (2ms)
[BG(1)] indexing: c:\Users\cires\Documents\GitHub\stock_trader\noxfile.py (0ms)
Background analysis message: markFilesDirty
Background analysis message: markFilesDirty
Background analysis message: markFilesDirty
Background analysis message: analyze
[BG(1)] analyzing: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\invariant_type_change.py ...
[BG(1)]   checking: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\invariant_type_change.py ...
[BG(1)]     parsing: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\typing.pyi [fs read 0ms] (11ms)
[BG(1)]     binding: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\typing.pyi (5ms)
[BG(1)]     parsing: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\_typeshed\__init__.pyi [fs read 1ms] (5ms)
[BG(1)]     binding: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\_typeshed\__init__.pyi (1ms)
[BG(1)]     parsing: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\typing_extensions.pyi [fs read 0ms] (2ms)
[BG(1)]     binding: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\typing_extensions.pyi (1ms)
[BG(1)]     parsing: c:\Users\cires\Documents\GitHub\stock_trader\.venv\Lib\site-packages\icontract\__init__.py [fs read 1ms] (8ms)
[BG(1)]     binding: c:\Users\cires\Documents\GitHub\stock_trader\.venv\Lib\site-packages\icontract\__init__.py (0ms)
[BG(1)]     parsing: c:\Users\cires\Documents\GitHub\stock_trader\.venv\Lib\site-packages\icontract\_decorators.py [fs read 1ms] (8ms)
[BG(1)]     binding: c:\Users\cires\Documents\GitHub\stock_trader\.venv\Lib\site-packages\icontract\_decorators.py (1ms)
[BG(1)]     parsing: c:\Users\cires\Documents\GitHub\stock_trader\.venv\Lib\site-packages\icontract\_globals.py [fs read 0ms] (1ms)
[BG(1)]     binding: c:\Users\cires\Documents\GitHub\stock_trader\.venv\Lib\site-packages\icontract\_globals.py (0ms)
[BG(1)]     parsing: c:\Users\cires\Documents\GitHub\stock_trader\.venv\Lib\site-packages\icontract\_metaclass.py [fs read 0ms] (4ms)
[BG(1)]     binding: c:\Users\cires\Documents\GitHub\stock_trader\.venv\Lib\site-packages\icontract\_metaclass.py (2ms)
[BG(1)]     parsing: c:\Users\cires\Documents\GitHub\stock_trader\.venv\Lib\site-packages\icontract\_types.py [fs read 0ms] (1ms)
[BG(1)]     binding: c:\Users\cires\Documents\GitHub\stock_trader\.venv\Lib\site-packages\icontract\_types.py (1ms)
[BG(1)]     parsing: c:\Users\cires\Documents\GitHub\stock_trader\.venv\Lib\site-packages\icontract\errors.py [fs read 0ms] (0ms)
[BG(1)]     binding: c:\Users\cires\Documents\GitHub\stock_trader\.venv\Lib\site-packages\icontract\errors.py (0ms)
[BG(1)]     parsing: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\reprlib.pyi [fs read 0ms] (1ms)
[BG(1)]     binding: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\reprlib.pyi (0ms)
[BG(1)]     parsing: c:\Users\cires\Documents\GitHub\stock_trader\.venv\Lib\site-packages\icontract\_checkers.py [fs read 1ms] (10ms)
[BG(1)]     binding: c:\Users\cires\Documents\GitHub\stock_trader\.venv\Lib\site-packages\icontract\_checkers.py (6ms)
[BG(1)]     parsing: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\os\__init__.pyi [fs read 1ms] (11ms)
[BG(1)]     binding: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\os\__init__.pyi (3ms)
[BG(1)]     parsing: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\abc.pyi [fs read 0ms] (0ms)
[BG(1)]     binding: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\abc.pyi (1ms)
[BG(1)]   checking: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\invariant_type_change.py (106ms)
[BG(1)] analyzing: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\invariant_type_change.py (106ms)
Background analysis message: resumeAnalysis
[BG(1)] parsing: c:\Users\cires\Documents\GitHub\stock_trader\docs\conf.py [fs read 0ms] (1ms)
[BG(1)] binding: c:\Users\cires\Documents\GitHub\stock_trader\docs\conf.py (0ms)
[BG(1)] indexing: c:\Users\cires\Documents\GitHub\stock_trader\docs\conf.py [found 6] (0ms)
Indexing Done: c:\Users\cires\Documents\GitHub\stock_trader\docs\conf.py
[BG(1)] parsing: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\Quote.py [fs read 0ms] (1ms)
[BG(1)] binding: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\Quote.py (0ms)
[BG(1)] indexing: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\Quote.py [found 4] (0ms)
Indexing Done: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\Quote.py
[BG(1)] parsing: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\__init__.py [fs read 1ms] (1ms)
[BG(1)] binding: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\__init__.py (0ms)
[BG(1)] indexing: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\__init__.py [found 0] (0ms)
Indexing Done: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\__init__.py
[BG(1)] parsing: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\__main__.py [fs read 0ms] (1ms)
[BG(1)] binding: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\__main__.py (0ms)
[BG(1)] indexing: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\__main__.py [found 2] (0ms)
Indexing Done: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\__main__.py
[BG(1)] parsing: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\broker_protocols.py [fs read 1ms] (4ms)
[BG(1)] binding: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\broker_protocols.py (1ms)
[BG(1)] indexing: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\broker_protocols.py [found 2] (0ms)
Indexing Done: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\broker_protocols.py
[BG(1)] indexing: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\invariant_type_change.py [found 6] (0ms)
Indexing Done: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\invariant_type_change.py
[BG(1)] parsing: c:\Users\cires\Documents\GitHub\stock_trader\tests\__init__.py [fs read 1ms] (1ms)
[BG(1)] binding: c:\Users\cires\Documents\GitHub\stock_trader\tests\__init__.py (0ms)
[BG(1)] indexing: c:\Users\cires\Documents\GitHub\stock_trader\tests\__init__.py [found 0] (0ms)
Indexing Done: c:\Users\cires\Documents\GitHub\stock_trader\tests\__init__.py
[BG(1)] parsing: c:\Users\cires\Documents\GitHub\stock_trader\tests\test_main.py [fs read 0ms] (2ms)
[BG(1)] binding: c:\Users\cires\Documents\GitHub\stock_trader\tests\test_main.py (1ms)
[BG(1)] indexing: c:\Users\cires\Documents\GitHub\stock_trader\tests\test_main.py [found 2] (0ms)
Indexing Done: c:\Users\cires\Documents\GitHub\stock_trader\tests\test_main.py
Background analysis message: getDiagnosticsForRange
Background analysis message: getDiagnosticsForRange
[FG] parsing: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\invariant_type_change.py (139ms)
[FG] parsing: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\builtins.pyi [fs read 1ms] (77ms)
[FG] binding: c:\Users\cires\.vscode\extensions\ms-python.vscode-pylance-2021.8.2-pre.1\dist\typeshed-fallback\stdlib\builtins.pyi (23ms)
[FG] binding: c:\Users\cires\Documents\GitHub\stock_trader\src\stock_trader\invariant_type_change.py (0ms)

Code Snippet / Additional information

from dataclasses import dataclass
from icontract import invariant


class TestClass1:
    test_int: int = 1


reveal_type(TestClass1)
# According to PyLance: Type[TestClass1]
# According to mypy: src\stock_trader\invariant_type_change.py:9:13: note: Revealed type is "def () -> stock_trader.invariant_type_change.TestClass1"

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


reveal_type(TestClass2)
# According to PyLance: type
# According to mypy: src\stock_trader\invariant_type_change.py:18:13: note: Revealed type is "def () -> stock_trader.invariant_type_change.TestClass2"


@dataclass
class TestClass3:
    test_class4: TestClass1
    test_class5: TestClass2 # PyLance states: Expected "class" type but received type


instance_of_TestClass1 = TestClass1()
instance_of_TestClass2 = TestClass2()

print(type(instance_of_TestClass1))
# <class '__main__.TestClass1'>
print(type(instance_of_TestClass2))
# <class '__main__.TestClass2'>

print(instance_of_TestClass1.test_int)
print(instance_of_TestClass2.test_int) # PyLance states test_int is of type Any

class_holder = TestClass3(
    test_class4=instance_of_TestClass1, test_class5=instance_of_TestClass2
)
print(class_holder.test_class4.test_int)
print(class_holder.test_class5.test_int)
"""When trying to print class_holder.test_class5.test_int, PyLance has the following 3 complaints:
Type of "test_int" is unknown.
Argument type is unknown.
Cannot access member "test_int" for type "type"
"""

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions