Skip to content

Conditional expression does not infer the type of an empty list from the other branch #4741

Description

@h3har

Description

Pyrefly infers an unannotated conditional expression containing a typed list (i.e., list[T]) and an empty list as list[T] | list[Unknown] rather than list[T]. This is inconsistent with how short-circuited or expressions are handled already.

Version: pyrefly 1.2.0

Reproduction

Sandbox Link

from typing import assert_type, reveal_type

def condition() -> bool:
    return True

result = list[int]() if condition() else []

reveal_type(result)
assert_type(result, list[int])

Running:

pyrefly check repro.py

reports that result has type list[int] | list[Unknown], and the assert_type call fails.

Expected behavior

I would expect result to be inferred as list[int]. The empty-list branch can be contextually typed from the list[int] branch. Pyright also infers list[int] for this expression.

Empty containers in conditional expressions are common, for example:

values = get_values() if should_fetch() else []

Additional context

Pyrefly already performs similar contextual inference for a boolean operation:

result = list[int]() or []

reveal_type(result)  # list[int]
assert_type(result, list[int])

An explicit expected type also makes the conditional expression type-check:

result: list[int] = list[int]() if condition() else []

This suggests that the conditional-expression branches are currently inferred independently when there is no expected type for the overall expression, whereas boolean-operation operands can provide a contextual hint to later operands.

I could not find an existing issue for this exact conditional-expression case. #4301 appears related because it also concerns contextual typing of an empty container, but it covers a different situation involving an explicit annotation.

Conclusion

This is an extremely common pattern that other type checkers explicitly support. This is not incompatible with pyrefly's first-use policy for inferring type parameters, but would be an additional heuristic that could be layered on top in cases where a gradual type (e.g., list[Unknown]) is unioned with an exact type (e.g., list[int]) as a result of control flow. Pyrefly already does this in certain cases, but the rule doesn't seem to be applied consistently.

Whether this should just apply to conditional expressions or also to if-else statements and so on is a design decision that I do not specify here (pyright seems to apply this to statements as well), but I think bringing if-else expressions to parity with short-circuited or expressions is a reasonable start.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    contextual-typingissues related to contextual typing

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions