Skip to content

Use a variadic tuple context to infer precise tuple types - #21779

Open
adhavan18 wants to merge 1 commit into
python:masterfrom
adhavan18:fix-21476-variadic-tuple-context
Open

Use a variadic tuple context to infer precise tuple types#21779
adhavan18 wants to merge 1 commit into
python:masterfrom
adhavan18:fix-21476-variadic-tuple-context

Conversation

@adhavan18

Copy link
Copy Markdown

Fixes #21476.

(*a, "last") where a: tuple[str, ...] is not accepted against the context tuple[str, *tuple[str, ...]], while the mirrored ("first", *a) is:

a: tuple[str, ...]
b: tuple[str, *tuple[str, ...]]
b = ("first", *a)  # ok
b = (*a, "last")   # error: Incompatible types in assignment (expression has
                   # type "tuple[str, ...]", variable has type
                   # "tuple[str, *tuple[str, ...]]")

tuple_context_matches() only accepts a variadic context when the structure lines up exactly — the star item has to sit at the same index as the context unpack:

return len(expr.items) == len(ctx.items) and ctx_unpack_index == expr_star_index

For (*a, "last") the unpack is at index 1 in the context but the star is at index 0, so the context is rejected. type_context_items is then never set, unpack_in_context stays False, and so allow_precise_tuples is False too. The star item therefore skips the precise-tuple branch and falls through to check_lst_expr(e, "builtins.tuple", "<tuple>"), giving tuple[str, ...] — which is not assignable back to a context requiring at least one item.

This decouples "is the context variadic" from "does the structure match exactly", and uses the former to enable precise tuple inference. unpack_in_context is deliberately left alone, because the j index bookkeeping further down is explicitly documented as depending on a full structure match.

With the change, the star item is inferred as Unpack[tuple[str, ...]], so the expression types as tuple[*tuple[str, ...], str] and the assignment is accepted. This matches what --enable-incomplete-feature=PreciseTupleTypes already produced for this expression, and lines up with the existing # TODO: try using tuple type context in more cases. next to the structural check.

It is a narrowing of what is rejected, not a widening of what is accepted — incompatible item types and too-short tuples still error, and the diagnostics get slightly better because the reported expression type is now precise:

- expression has type "tuple[str, ...]",           variable has type "tuple[int, *tuple[int, ...]]"
+ expression has type "tuple[*tuple[str, ...], str]", variable has type "tuple[int, *tuple[int, ...]]"

Tests

Added testVariadicTupleContextStarPositionMismatch to check-typevar-tuple.test, covering both star positions plus a wrong-item-type case and a too-short case so the change can't silently start accepting bad code. It fails on master and passes with this change.

Full mypy/test/testcheck.py suite: 8173 passed, 15 skipped, 7 xfailed.

One case intentionally not addressed: (*a, *a, "x") still collapses to tuple[str, ...], since a tuple type can only carry one unpack. That behaves the same before and after this change.

When a tuple expression is checked against a variadic tuple context,
tuple_context_matches() only accepts the context if the structure lines
up exactly, i.e. the star item sits at the same index as the context
unpack. For (*a, "last") against tuple[str, *tuple[str, ...]] it does
not, so no context was used, allow_precise_tuples stayed off, and the
expression collapsed to tuple[str, ...] -- which is then not assignable
back to the variadic context.

Derive the 'is the context variadic' question separately from the exact
structural match and use it to enable precise tuple inference. This is
kept out of unpack_in_context, whose index bookkeeping still relies on a
full structure match.

Fixes python#21476.
@github-actions

Copy link
Copy Markdown
Contributor

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Literal (*tuple_of_strings, "string") not assignable to tuple[str, *tuple[str, ...]]

1 participant