Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

False positive E1126 (invalid-sequence-index) on generic type alias with forward ref #9908

Open
dairiki opened this issue Sep 2, 2024 · 3 comments
Labels
False Positive 🦟 A message is emitted but nothing is wrong with the code typing

Comments

@dairiki
Copy link

dairiki commented Sep 2, 2024

Bug description

# pylint: disable=missing-docstring
from typing import TypeAlias, TypeVar

T = TypeVar("T")

Alias1: TypeAlias = list[T]
Alias2: TypeAlias = "list[T]"

x1: Alias1[int]  # ok
x2: Alias2[int]  # <= triggers E1126 (invalid-sequence-index)

Configuration

No response

Command used

pylint --rcfile /dev/null test.py

Pylint output

************* Module test
test.py:10:4: E1126: Sequence index is not an int, slice, or instance with __index__ (invalid-sequence-index)

------------------------------------------------------------------
Your code has been rated at 1.67/10 (previous run: 1.67/10, +0.00)

Expected behavior

Should not emit warning.

I've also tested with pylint from the head of the main branch (eb33f8a).

Pylint version

pylint 3.2.7
astroid 3.2.4
Python 3.12.5 (main, Aug  7 2024, 10:47:46) [GCC 11.4.0]

OS / Environment

Ubuntu 22.04

Additional dependencies

No response

@dairiki dairiki added the Needs triage 📥 Just created, needs acknowledgment, triage, and proper labelling label Sep 2, 2024
@nickdrozd
Copy link
Collaborator

Running this code raises a type error:

python3 asdf.py

Traceback (most recent call last):
  File "/pylint/asdf.py", line 9, in <module>
    x2: Alias2[int]  # <= triggers E1126 (invalid-sequence-index)
        ~~~~~~^^^^^
TypeError: string indices must be integers, not 'type'

So this looks like a true positive, i.e. Pylint is behaving correctly by warning about an actual error.

@dairiki
Copy link
Author

dairiki commented Sep 6, 2024

Running this code raises a type error:
So this looks like a true positive, i.e. Pylint is behaving correctly by warning about an actual error.

My apologies, you're right. I oversimplified my failure test case. It fails without PEP563 deferred evaluation of annotations.
(I.e. it needs a from __future__ import annotations.)

Try this one:

# pylint: disable=missing-docstring
from __future__ import annotations

from typing import TypeAlias, TypeVar

T = TypeVar("T")

Alias1: TypeAlias = list[T]
Alias2: TypeAlias = "list[T]"

x1: Alias1[int]  # ok
x2: Alias2[int]  # <= triggers E1126 (invalid-sequence-index)

To add motivation as to why one might want to do this, here's a slightly less contrived example that fails pylint validation the same way:

# pylint: disable=missing-docstring,too-few-public-methods
from __future__ import annotations

from typing import Generic, TypeAlias, TypeVar

T = TypeVar("T")

MaybeWrapped: TypeAlias = "Wrapped[T] | T"


class Wrapped(Generic[T]):
    value: T

    def __init__(self, v: MaybeWrapped[T], /) -> None:  # <= triggers E1126
        self.value = v.value if isinstance(v, Wrapped) else v

@nickdrozd
Copy link
Collaborator

Thanks, I see the problem now. Looks like Pylint thinks the TypeAlias is just a string? TBH I don't understand how the string-as-annotation system works. Mypy accepts it fine.

As a workaround, you could work around the bug by moving MaybeWrapped after Wrapped and not using string-as-annotation:

from __future__ import annotations

from typing import Generic, TypeAlias, TypeVar

T = TypeVar("T")

class Wrapped(Generic[T]):
    value: T

    def __init__(self, v: MaybeWrapped[T], /) -> None:  # <= triggers E1126
        self.value = v.value if isinstance(v, Wrapped) else v

MaybeWrapped: TypeAlias = Wrapped[T] | T

Or in 3.12, you can avoid all that mess by using the type keyword:

from __future__ import annotations

type MaybeWrapped[T] = Wrapped[T] | T

class Wrapped[T]:
    value: T

    def __init__(self, v: MaybeWrapped[T], /) -> None:
        self.value = v.value if isinstance(v, Wrapped) else v

@nickdrozd nickdrozd added False Positive 🦟 A message is emitted but nothing is wrong with the code typing and removed Needs triage 📥 Just created, needs acknowledgment, triage, and proper labelling labels Sep 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
False Positive 🦟 A message is emitted but nothing is wrong with the code typing
Projects
None yet
Development

No branches or pull requests

2 participants