Skip to content

Type checker fails to recognize a module as a valid implementation of a Protocol #931

@wvlab

Description

@wvlab

Summary

The ty type checker appears to incorrectly flag a type error when a module is used to satisfy a typing.Protocol. According to Python's official typing documentation, modules can serve as implementations for protocols, a feature that is correctly handled by other type checkers like Mypy and Pyright.

When running mypy, pyright or pyrefly on the project, type checkers pass without any errors, correctly identifying that the bar module is a valid implementation of the Proto protocol.

Code to Reproduce

Here is a minimal set of code that demonstrates the issue.

  1. The Protocol Definition (behavior.py):
    A simple protocol that requires a foo function.

    from typing import Protocol
    
    class Proto(Protocol):
        def foo(self, x: int) -> str: ...
  2. The Module Implementation (bar.py):
    A module with a function that structurally conforms to the protocol.

    def foo(x: int) -> str:
        return f"6{x}9"
  3. The Type Check (main.py):
    The bar module is assigned to a variable annotated with the Proto type.

    import behavior
    import bar
    
    behavior.consume(bar)
    _: behavior.Proto = bar

Full Reproducible Example

You can reproduce it in playground

I have also uploaded the complete, self-contained project to a public GitHub repository. You can clone it and run the type checkers to see the issue firsthand: wvlab/example-python-module-protocols

To reproduce:

git clone https://github.com/wvlab/example-python-module-protocols.git mre && cd mre
uv sync --frozen
source .venv/bin/activate
./run_type_checkers.sh

ty Output

> ty check
error[invalid-argument-type]: Argument to function `consume` is incorrect
  --> src/example_module_protocols/main.py:9:28
   |
 8 | def main() -> None:
 9 |     print(behavior.consume(bar))
   |                            ^^^ Expected `Proto`, found `<module 'example_module_protocols.implementations.bar'>`
10 |     _: behavior.Proto = bar
11 |     return
   |
info: Function defined here
  --> src/example_module_protocols/behavior.py:9:5
   |
 7 |     def foo(self, x: int) -> str: ...
 8 |
 9 | def consume(proto: Proto) -> str:
   |     ^^^^^^^ ------------ Parameter declared here
10 |     return proto.foo(5)
   |
info: rule `invalid-argument-type` is enabled by default

error[invalid-assignment]: Object of type `<module 'example_module_protocols.implementations.bar'>` is not assignable to `Proto`
  --> src/example_module_protocols/main.py:10:5
   |
 8 | def main() -> None:
 9 |     print(behavior.consume(bar))
10 |     _: behavior.Proto = bar
   |     ^
11 |     return
   |
info: rule `invalid-assignment` is enabled by default

Version

ty 0.0.1-alpha.16 (e48b66f 2025-07-31)

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions