Skip to content

Commit

Permalink
Fix Y053 false positives inside Literal and Annotated slices (#497)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexWaygood authored Aug 20, 2024
1 parent 72a2de5 commit 91b163d
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 4 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log

## Unreleased

Bugfixes
* Don't emit Y053 for long strings inside `Literal` slices or
metadata strings inside `Annotated` slices.

## 24.6.0

Bugfixes
Expand Down
2 changes: 1 addition & 1 deletion ERRORCODES.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ The following warnings are currently emitted by default:
| <a id="Y050" href="#Y050">Y050</a> | Prefer `typing_extensions.Never` over `typing.NoReturn` for argument annotations. | Style
| <a id="Y051" href="#Y051">Y051</a> | Y051 detects redundant unions between `Literal` types and builtin supertypes. For example, `Literal[5]` is redundant in the union `int \| Literal[5]`, and `Literal[True]` is redundant in the union `Literal[True] \| bool`. | Redundant code
| <a id="Y052" href="#Y052">Y052</a> | Y052 disallows assignments to constant values where the assignment does not have a type annotation. For example, `x = 0` in the global namespace is ambiguous in a stub, as there are four different types that could be inferred for the variable `x`: `int`, `Final[int]`, `Literal[0]`, or `Final[Literal[0]]`. Enum members are excluded from this check, as are various special assignments such as `__all__` and `__match_args__`. | Correctness
| <a id="Y053" href="#Y053">Y053</a> | Only string and bytes literals <=50 characters long are permitted. | Style
| <a id="Y053" href="#Y053">Y053</a> | Only string and bytes literals <=50 characters long are permitted. (There are some exceptions, such as `Literal` subscripts, metadata strings inside `Annotated` subscripts, and strings passed to `@deprecated`.) | Style
| <a id="Y054" href="#Y054">Y054</a> | Only numeric literals with a string representation <=10 characters long are permitted. | Style
| <a id="Y055" href="#Y055">Y055</a> | Unions of the form `type[X] \| type[Y]` can be simplified to `type[X \| Y]`. Similarly, `Union[type[X], type[Y]]` can be simplified to `type[Union[X, Y]]`. | Style
| <a id="Y056" href="#Y056">Y056</a> | Do not call methods such as `.append()`, `.extend()` or `.remove()` on `__all__`. Different type checkers have varying levels of support for calling these methods on `__all__`. Use `+=` instead, which is known to be supported by all major type checkers. | Correctness
Expand Down
5 changes: 3 additions & 2 deletions pyi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1563,7 +1563,8 @@ def _visit_typing_Literal(self, node: ast.Subscript) -> None:
suggestion = f"Literal[{new_literal_slice}] | None"
self.error(analysis.none_members[0], Y061.format(suggestion=suggestion))

self.visit(node.slice)
with self.long_strings_allowed.enabled():
self.visit(node.slice)

def _visit_slice_tuple(self, node: ast.Tuple, parent: str | None) -> None:
if parent == "Union":
Expand All @@ -1573,7 +1574,7 @@ def _visit_slice_tuple(self, node: ast.Tuple, parent: str | None) -> None:
# Allow literals, except in the first argument
if len(node.elts) > 1:
self.visit(node.elts[0])
with self.string_literals_allowed.enabled():
with self.string_literals_allowed.enabled(), self.long_strings_allowed.enabled():
for elt in node.elts[1:]:
self.visit(elt)
else:
Expand Down
8 changes: 8 additions & 0 deletions tests/defaults.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import math
import os
import sys
from typing import Annotated, Literal, TypeAlias

import _typeshed
from _typeshed import SupportsRead, SupportsWrite, sentinel
Expand Down Expand Up @@ -84,3 +85,10 @@ def f40(x: int, /) -> None: ...
def f41(x: int, /, y: "int") -> None: ... # Y020 Quoted annotations should never be used in stubs
def f42(x: str = "y", /) -> None: ...
def f43(x: str = os.pathsep, /) -> None: ... # Y011 Only simple default values allowed for typed arguments

# Long strings inside `Literal` or `Annotated` slices are okay
def f44(x: Literal["loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong"]) -> None: ...
def f55(x: Annotated[str, "metadataaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]) -> None: ...

X: TypeAlias = Literal["llllllllllllllllllllllllllonggggggggggggggggggggggggggggggggggggggggg"]
Y: TypeAlias = Annotated[int, "verrrrrrrrrrrrrrrrrrrry looooooooooooooongggggggggggggggggggggggggggggggggggg"]
14 changes: 13 additions & 1 deletion tests/pep695_py312.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@

import typing
from collections.abc import Iterator
from typing import Any, NamedTuple, NoReturn, Protocol, Self, TypedDict
from typing import (
Annotated,
Any,
Literal,
NamedTuple,
NoReturn,
Protocol,
Self,
TypedDict,
)

type lowercase_alias = str | int # Y042 Type aliases should use the CamelCase naming convention
type _LooksLikeATypeVarT = str | int # Y043 Bad name for a type alias (the "T" suffix implies a TypeVar)
Expand Down Expand Up @@ -66,3 +75,6 @@ class _UnusedPEP695Protocol[T](Protocol): # Y046 Protocol "_UnusedPEP695Protoco

class _UnusedPEP695TypedDict[T](TypedDict): # Y049 TypedDict "_UnusedPEP695TypedDict" is not used
x: T

type X = Literal["Y053 will not be emitted here despite it being a very long string literal, because it is inside a `Literal` slice"]
type Y = Annotated[int, "look at me, very long string literal, but it's okay because it's an `Annotated` metadata string"]

0 comments on commit 91b163d

Please sign in to comment.