Skip to content

error: "x" does not return a value [func-returns-value] #15785

@alexei

Description

@alexei

It's been suggested before that mypy makes no distinction between a function that doesn't return anything and a function that returns None explicitly, and simply assumes no return value. I think I understand mypy's approach on this since in practice there's no difference between return, return None, and no return statement at all. I also think it's not fair considering a NoReturn is also available, though I suspect it's because Python's history is longer than that of NoReturn.

But consider a hypothetical example when a developer needs to return None:

@dataclass
class IntBox:
    value: int

    def val(self) -> int:
        return self.value


class EmptyBox:
    def val(self) -> None:
        return None


assert IntBox(1).val() == 1
assert EmptyBox().val() is None

As expected, mypy points out an error: "val" of "EmptyBox" does not return a value [func-returns-value]

In my case such assertions were part of the tests, though I see no harm in wanting to call val anywhere without caring about the return value. E.g. I just want to store the result in a nullable field in a database table.

Considering mypy's current behavior, my solution now is to instruct it to ignore that specific line:

assert EmptyBox().val() is None  # type: ignore

Another option would be to use a sentinel value, but that feels like it's getting out of hand.

My understanding is mypy takes a conservative approach here, so what if there was an alternate mechanism to telling it the developer actually wanted to return a None? For example, what if it considered Literal[None] as a type hint distinct from None? I.e:

class EmptyBox:
    def val(self) -> Literal[None]:
        return None

This would allow mypy (and similar tools - I presume) to keep working as they were, while still allowing an explicit None return value. It looks, at least on the outside, that similar behavior exists for the boolean values:

def the_truth() -> True:
    ...

is not allowed i.e. error: Invalid type: try using Literal[True] instead? [valid-type], but a literal True is:

def the_truth() -> Literal[True]:
    ...

While writing this, I'm learning there exists a Never as well. I think Never is interesting, but would not be suitable in my example as I do intend to call val() on an empty box at some point, whereas Never doesn't like return statements.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions