Skip to content

Commit 9d69429

Browse files
Preserve Annotated annotations on access to methods of literals (#541)
1 parent 84d9dbf commit 9d69429

File tree

3 files changed

+39
-0
lines changed

3 files changed

+39
-0
lines changed

docs/changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Preserve `Annotated` annotations on access to methods of
6+
literals (#541)
57
- `allow_call` callables are now also called if the arguments
68
are literals wrapped in `Annotated` (#540)
79
- Support Python 3.11 (#537)

pyanalyze/attributes.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,16 @@ def _get_attribute_from_known(obj: object, ctx: AttrContext) -> Value:
348348
return GenericValue(dict, [TypedValue(str), TypedValue(types.ModuleType)])
349349

350350
result, _, _ = _get_attribute_from_mro(obj, ctx, on_class=True)
351+
if (
352+
isinstance(result, KnownValue)
353+
and (
354+
safe_isinstance(result.val, types.MethodType)
355+
or safe_isinstance(result.val, types.BuiltinFunctionType)
356+
and result.val.__self__ is obj
357+
)
358+
and isinstance(ctx.root_value, AnnotatedValue)
359+
):
360+
result = UnboundMethodValue(ctx.attr, ctx.root_composite)
351361
if safe_isinstance(obj, type):
352362
result = set_self(result, TypedValue(obj))
353363
if isinstance(obj, (types.ModuleType, type)):

pyanalyze/test_attributes.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
from .test_name_check_visitor import TestNameCheckVisitorBase
55
from .test_node_visitor import assert_passes, only_before
66
from .value import (
7+
AnnotatedValue,
78
AnySource,
89
AnyValue,
10+
UnboundMethodValue,
911
assert_is_value,
1012
GenericValue,
1113
KnownValue,
@@ -170,6 +172,31 @@ def test(x: Union[Capybara, Paca]) -> None:
170172
x.attr, MultiValuedValue([TypedValue(int), TypedValue(str)])
171173
)
172174

175+
@assert_passes()
176+
def test_annotated_known(self):
177+
from typing_extensions import Annotated, Literal
178+
from pyanalyze.extensions import LiteralOnly
179+
from pyanalyze.stacked_scopes import Composite, VarnameWithOrigin
180+
from pyanalyze.value import CustomCheckExtension
181+
from qcore.testing import Anything
182+
183+
origin = VarnameWithOrigin("encoding", Anything) # E: incompatible_argument
184+
185+
def capybara():
186+
encoding: Annotated[Literal["ascii"], LiteralOnly()] = "ascii"
187+
assert_is_value(
188+
encoding.encode,
189+
UnboundMethodValue(
190+
"encode",
191+
Composite(
192+
AnnotatedValue(
193+
KnownValue("ascii"), [CustomCheckExtension(LiteralOnly())]
194+
),
195+
origin,
196+
),
197+
),
198+
)
199+
173200
@assert_passes()
174201
def test_optional_operation(self):
175202
from typing import Optional

0 commit comments

Comments
 (0)