basedpyright icon indicating copy to clipboard operation
basedpyright copied to clipboard

`reportPossiblyUnboundVariable` confused by literals

Open AlexBlandin opened this issue 1 year ago • 1 comments

Version: BasedPyright v1.12.0

basedpyright(reportPossiblyUnboundVariable)

As seen below, the snippet has these errors on a, b.

These are well bounded by their literals, so assessing the literal as non-empty (or not) seems to be the limitation.

for c in "foo":
  a = c

for c in ("bar",):
  b = c

print(a, b)
"a" is possibly unbound
"b" is possibly unbound

AlexBlandin avatar May 08 '24 14:05 AlexBlandin

i think these are 2 separate unrelated issues:

"a" is possibly unbound
"b" is possibly unbound

basedpyright(reportPossiblyUnboundVariable)

As seen below, the snippet has these errors on a, b.

These are well bounded by their literals, so assessing the literal as non-empty (or not) seems to be the limitation.

looks like this is an upstream issue that was rejected (https://github.com/microsoft/pyright/issues/7822). i don't see why pyright shouldn't be able to statically determine how many times a tuple with a defined length will be iterated over, because this information is available in the type system.

In addition, b = does not receive an annotation inlay, whereas d does. While this makes sense in cases like e vs. f below, as the "trivial" case of literal assignment doesn't need an inlay, anything more complicated than direct assignment arguably should receive an inlay, as it is still useful for cases of "indirect assignment", where b = receiving an infill could be considered helpful, for the purposes of making it obvious that it is a literal. Similarly, with h = as the indirection of g, it is likely still meaningful to know the result is a literal.

Based on the behaviour, I imagine the rule is to show a "helpful" inlay, and that "just" a Literal[...] is not considered helpful by default. A configuration option for this may be useful, though likely following different modes akin to the type checking; perhaps the current rule is basic/standard and we have strict above that for cases like b = and h = , with an all for even showing inlays on assignments like e = "baz".

for c in "foo":
  a = c  # a: LiteralString

for c in ("bar",):
  b = c  # no inlay

print(a, b)
for c in (1, 2, 3):
  d = c  # d: Literal[1, 2, 3]

e = "baz"  # no inlay
f = ("baz",)  # f: tuple[Literal['baz']]


def g():  # g() -> Literal['baz']
  return "baz"


h = g()  # no inlay

this is caused by some heuristic logic that tries to prevent redundant information from being shown in an inlay hint. specifically, we don't display inlay hints for Literals because in this example, the inlay hint : Literal["bar"] would be completely useless because the value is already clearly visible in the string literal:

value = "bar"

but obviously not all Literals are like that. i'll move this to a separate issue: #349

DetachHead avatar May 08 '24 14:05 DetachHead

ideally this would also work on range:

for _ in range(1):
    a = 1
print(a)  # error: possibly undefined

edit: moved this to a separate issue: #815

DetachHead avatar Jun 13 '24 09:06 DetachHead