Skip to content

How to make pylance respect a "require_type" function #5771

Closed
@jaxwagner

Description

@jaxwagner

Hi! Hope you are well :)

In the project I am working on, it is important for me to be able to create a function that takes in a parameter of Optional[T] type and return that same var of type T if it is not None, else raise a RuntimeError. This way I can access properties of the expected object when I know that it should not be None (e.g., after everything is initialized).

Here is a simple example to illustrate this point:

import random
from typing import NoReturn, Optional, TypeVar, overload

T = TypeVar("T")


class MyClass:
    def __init__(self, property: int):
        self.property: int = property


def get_obj_or_none() -> Optional[MyClass]:
    obj = MyClass(1)
    return obj if random.random() > 0.001 else None


@overload
def require_type(x: None) -> NoReturn:
    ...


@overload
def require_type(x: T) -> T:
    ...


def require_type(x: Optional[T]) -> T:
    if x is None:
        raise RuntimeError("x is None")
    return x


x = get_obj_or_none()  # x should have type str | None
y = require_type(x)  # y should have type str
y_property = y.property # property is accessible and correctly typed as int
print(y_property)

In this example, I find that y, although it I want it to be typed as MyClass, is typed as MyClass | None by pylance / pyright. See screenshot below:
image

However, when I commend out the overloads, it is typed correctly:
image

This makes sense based on the pyright overload handling rules I found here: https://microsoft.github.io/pyright/#/type-concepts-advanced?id=overloads
Since the input to the require_type function is of union type (Optional[T] is syntax for T | Non), I fall into case 5 in the documentation and the following happens:

If so, these union types are expanded into their constituent subtypes, and the entire process of overload matching is repeated with the expanded argument types. If two or more overloads match, the union of their respective return types form the final return type for the call expression.

This is problematic for me, because I also use mypy and I need the overloads structured in this way to pass mypy type checking and also ensure that mypy correctly interprets the types of variables after I use the require_type function python/mypy#9424 . Further, it says here #2660 (reply in thread) that at least as of Dec 2023 there is no plan to conform the way mypy and pyright handle overloads.

Thus, my question is, how can I require both pyright / pylance and mypy to interpret variables as non-optional after I call some kind of require_type function on said variable? If there is no other way to do this specifically, how can i solve this problem of needing to caste optional variables as non-None?

Thank you!

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions