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

Pylance cannot read type informations from .pyi file at the same level of directory #2665

Closed
nukemiko opened this issue Apr 20, 2022 · 1 comment
Labels
needs investigation Could be an issue - needs investigation

Comments

@nukemiko
Copy link

nukemiko commented Apr 20, 2022

Environment data

  • Language Server version: 2022.4.1
  • OS and version: Arch Linux
  • Python version (& distribution if applicable, e.g. Anaconda): 3.10.4 at /usr/bin/python3.10, without virtual environment
  • Project hierarchy:
    test
    └── src
        └── testpkg
            ├── __init__.py
            ├── __init__.pyi
            ├── util.py
            └── util.pyi
    
    2 directories, 4 files
    

Expected behavior

  • The type of function's parameters and return will normally display (screenshot from util.pyi):

Screenshot_20220420_202556
Screenshot_20220420_202613
Screenshot_20220420_204348

Actual behavior

  • The type of function's parameters and return displayed Unknown, unless the Pylance deduced them type from the code, like this (screenshot from util.py):

Screenshot_20220420_202639
Screenshot_20220420_202701
Screenshot_20220420_204414

Logs

I put it in the pastebin: https://pst.klgrth.io/paste/hogqcwwcfej7jjdg3yhr5bbf

Code Snippet / Additional information

utils.py:

def bytesxor(term1, term2):
    segment1 = bytes(term1)
    segment2 = bytes(term2)

    if len(segment1) != len(segment2):
        raise ValueError('only byte strings of equal length can be xored')

    def xor():
        for b1, b2 in zip(segment1, segment2):
            yield b1 ^ b2

    return bytes(xor())


def normally_return(obj):
    return obj


def string_modify(s):
    if not isinstance(s, str):
        raise TypeError(f"'{s}' must be str, not {type(s).__name__}")

    return s + '.modified'


def verify_fileobj(obj):
    if isinstance(obj, (str, bytes)) or hasattr(obj, '__fspath__'):
        raise ValueError(f"{repr(obj)} is not a file object")


def verify_readable(fileobj, is_binary=True):
    verify_fileobj(fileobj)
    target_type = str
    if is_binary:
        target_type = bytes

    fileobj_readable = getattr(fileobj, 'readable', None)
    if callable(fileobj_readable):
        if not fileobj_readable():
            raise ValueError(f"file {repr(fileobj)} is not readable")
    elif isinstance(fileobj, bool):
        if not fileobj_readable:
            raise ValueError(f"file {repr(fileobj)} is not readable")
    else:
        result = fileobj.read(0)
        if not isinstance(result, target_type):
            raise ValueError(
                f"incorrect type from file {repr(fileobj)} "
                f"(should be {target_type.__name__}, got {type(fileobj).__name__})"
            )


def verify_writable(fileobj, is_binary=True):
    verify_fileobj(fileobj)
    target_type = str
    if is_binary:
        target_type = bytes

    fileobj_writable = getattr(fileobj, 'writable', None)
    if callable(fileobj_writable):
        if not fileobj_writable():
            raise ValueError(f"file {repr(fileobj)} is not writable")
    elif isinstance(fileobj, bool):
        if not fileobj_writable:
            raise ValueError(f"file {repr(fileobj)} is not writable")
    else:
        fileobj.write(target_type())


def verify_seekable(fileobj):
    verify_fileobj(fileobj)

    fileobj_seekable = getattr(fileobj, 'seekable', None)
    if callable(fileobj_seekable):
        if not fileobj_seekable():
            raise ValueError(f"file {repr(fileobj)} is not seekable")
    elif isinstance(fileobj, bool):
        if not fileobj_seekable:
            raise ValueError(f"file {repr(fileobj)} is not seekable")
    else:
        fileobj.seek(0, 1)

utils.pyi:

from typing import IO, Iterable, SupportsBytes, SupportsIndex


def bytesxor(
        term1: SupportsBytes | Iterable[SupportsIndex],
        term2: SupportsBytes | Iterable[SupportsIndex]
) -> bytes:
    ...


def normally_return(obj: object) -> object:
    ...


def string_modify(s: str) -> str:
    ...


def verify_fileobj(obj: object) -> None:
    ...


def verify_readable(fileobj: IO, is_binaryio: bool = True) -> None:
    ...


def verify_writable(fileobj: IO, is_binaryio: bool = True) -> None:
    ...


def verify_seekable(fileobj: IO) -> None:
    ...

And the project files: test-project.zip

@judej judej added the needs investigation Could be an issue - needs investigation label Apr 20, 2022
@github-actions github-actions bot removed the triage label Apr 20, 2022
@heejaechang
Copy link
Contributor

heejaechang commented Apr 20, 2022

@nukemiko you get Unknown in verify_readable because you put IO which is generic type without type argument.
ex) def verify_readable(fileobj: IO[str], is_binaryio: bool = ...) -> None:
in non-strict mode, we will implicitly put Any as type argument, ex) IO[Any], but in strict mode, we won't do such implicit thing for you, so that's why you get "Unknown"

also, the reason you get unknown from utils.py is because it doesn't have type annotation. we don't merge type info even if you have pyi file for associated py file.
ex) utils.pyi type info won't be merged to utils.py.

the type info in utils.pyi will be used when symbols in utils are used in other file.
ex) foo.py using utils by "import utils"

if you want the effect of merging, best way is just inlining type annotation in utils.py rather than having separate pyi file.

thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs investigation Could be an issue - needs investigation
Projects
None yet
Development

No branches or pull requests

3 participants