Skip to content

Commit

Permalink
fix use of TypeVar in default (#378)
Browse files Browse the repository at this point in the history
  • Loading branch information
JelleZijlstra authored Dec 31, 2021
1 parent 893fcab commit 35b5980
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 14 deletions.
2 changes: 2 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Fix usage of type variables in function parameters
with defaults (#378)
- Support the Python 3.10 `match` statement (#376)
- Support the walrus (`:=`) operator (#375)
- Initial support for proposed new "type evaluation"
Expand Down
29 changes: 16 additions & 13 deletions pyanalyze/signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ def _check_param_type_compatibility(
- A Value or None, used for union decomposition with overloads.
"""
if param.annotation != UNANNOTATED and composite.value is not param.default:
if param.annotation != UNANNOTATED:
if typevar_map:
param_typ = param.annotation.substitute_typevars(typevar_map)
else:
Expand All @@ -404,18 +404,21 @@ def _check_param_type_compatibility(
param_typ, composite.value, visitor
)
if isinstance(tv_map, CanAssignError):
if is_overload:
triple = decompose_union(param_typ, composite.value, visitor)
if triple is not None:
return triple
visitor.show_error(
composite.node if composite.node is not None else node,
f"Incompatible argument type for {param.name}: expected {param_typ}"
f" but got {composite.value}",
ErrorCode.incompatible_argument,
detail=str(tv_map),
)
return None, False, None
if composite.value is param.default:
tv_map = {}
else:
if is_overload:
triple = decompose_union(param_typ, composite.value, visitor)
if triple is not None:
return triple
visitor.show_error(
composite.node if composite.node is not None else node,
f"Incompatible argument type for {param.name}: expected"
f" {param_typ} but got {composite.value}",
ErrorCode.incompatible_argument,
detail=str(tv_map),
)
return None, False, None
return tv_map, used_any, None
return {}, False, None

Expand Down
2 changes: 1 addition & 1 deletion pyanalyze/test_arg_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ async def capybara():
# annotated as def ... -> Future in typeshed
assert_is_value(
asyncio.sleep(3),
GenericValue(asyncio.Future, [AnyValue(AnySource.generic_argument)]),
GenericValue(asyncio.Future, [AnyValue(AnySource.unannotated)]),
)
return 42

Expand Down
18 changes: 18 additions & 0 deletions pyanalyze/test_signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,24 @@ class B(A):
def capybara(self) -> None:
super().capybara()

@assert_passes()
def test_default(self):
from typing import TypeVar, Dict, Union

KT = TypeVar("KT")
VT = TypeVar("VT")
T = TypeVar("T")

def dictget(d: Dict[KT, VT], key: KT, default: T = None) -> Union[VT, T]:
try:
return d[key]
except KeyError:
return default

def capybara(d: Dict[str, str], key: str) -> None:
assert_is_value(dictget(d, key), TypedValue(str) | KnownValue(None))
assert_is_value(dictget(d, key, 1), TypedValue(str) | KnownValue(1))


class TestAllowCall(TestNameCheckVisitorBase):
@assert_passes()
Expand Down

0 comments on commit 35b5980

Please sign in to comment.