Skip to content

Commit

Permalink
make reveal_type() behave like typing.reveal_type() will (#433)
Browse files Browse the repository at this point in the history
  • Loading branch information
JelleZijlstra authored Jan 23, 2022
1 parent 5cf7f4b commit 9879790
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 14 deletions.
3 changes: 3 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- `reveal_type()` and `dump_value()` now return their argument,
the anticipated behavior for `typing.reveal_type()` in Python
3.11 (#432)
- Fix return type of async generator functions (#431)
- Type check function decorators (#428)
- Handle `NoReturn` in `async def` functions (#427)
Expand Down
9 changes: 7 additions & 2 deletions pyanalyze/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,10 @@ def __call__(self) -> NoReturn:
raise NotImplementedError("just here to fool typing._type_check")


def reveal_type(value: object) -> None:
_T = TypeVar("_T")


def reveal_type(value: _T) -> _T:
"""Inspect the inferred type of an expression.
Calling this function will make pyanalyze print out the argument's
Expand All @@ -383,8 +386,10 @@ def reveal_type(value: object) -> None:
def f(x: int) -> None:
reveal_type(x) # Revealed type is "int"
At runtime this returns the argument unchanged.
"""
pass
return value


_overloads: Dict[str, List[Callable[..., Any]]] = defaultdict(list)
Expand Down
33 changes: 25 additions & 8 deletions pyanalyze/implementation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import typing
import typing_extensions
from .annotations import type_from_value
from .error_code import ErrorCode
from .extensions import reveal_type
Expand Down Expand Up @@ -938,23 +940,23 @@ def _assert_is_value_impl(ctx: CallContext) -> Value:


def _reveal_type_impl(ctx: CallContext) -> Value:
value = ctx.vars["value"]
if ctx.visitor._is_checking():
value = ctx.vars["value"]
message = f"Revealed type is {ctx.visitor.display_value(value)}"
ctx.show_error(message, ErrorCode.inference_failure, arg="value")
return KnownValue(None)
return value


def _dump_value_impl(ctx: CallContext) -> Value:
value = ctx.vars["value"]
if ctx.visitor._is_checking():
value = ctx.vars["value"]
message = f"Value is '{value!r}'"
if isinstance(value, KnownValue):
sig = ctx.visitor.arg_spec_cache.get_argspec(value.val)
if sig is not None:
message += f", signature is {sig!r}"
ctx.show_error(message, ErrorCode.inference_failure, arg="value")
return KnownValue(None)
return value


def _str_format_impl(ctx: CallContext) -> Value:
Expand Down Expand Up @@ -1104,6 +1106,7 @@ def _len_impl(ctx: CallContext) -> ImplReturn:
"encoding", annotation=TypedValue(str), default=KnownValue("")
)

T = TypeVar("T")
K = TypeVar("K")
V = TypeVar("V")

Expand Down Expand Up @@ -1132,14 +1135,14 @@ def get_default_argspecs() -> Dict[object, Signature]:
callable=assert_is_value,
),
Signature.make(
[SigParameter("value", _POS_ONLY)],
KnownValue(None),
[SigParameter("value", _POS_ONLY, annotation=TypeVarValue(T))],
TypeVarValue(T),
impl=_reveal_type_impl,
callable=reveal_type,
),
Signature.make(
[SigParameter("value", _POS_ONLY)],
KnownValue(None),
[SigParameter("value", _POS_ONLY, annotation=TypeVarValue(T))],
TypeVarValue(T),
impl=_dump_value_impl,
callable=dump_value,
),
Expand Down Expand Up @@ -1549,4 +1552,18 @@ def get_default_argspecs() -> Dict[object, Signature]:
),
),
]
for mod in typing, typing_extensions:
try:
reveal_type_func = getattr(mod, "reveal_type")
except AttributeError:
pass
else:
# Anticipating https://bugs.python.org/issue46414
sig = Signature.make(
[SigParameter("value", _POS_ONLY, annotation=TypeVarValue(T))],
TypeVarValue(T),
impl=_reveal_type_impl,
callable=reveal_type_func,
)
signatures.append(sig)
return {sig.callable: sig for sig in signatures}
10 changes: 10 additions & 0 deletions pyanalyze/test_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1174,6 +1174,16 @@ def capybara(val: Value) -> None:
assert_is_value(1, KnownValue(2)) # E: inference_failure
assert_is_value(1, val) # E: inference_failure

@assert_passes()
def test_return_value(self) -> None:
from pyanalyze import dump_value, assert_is_value

def capybara():
x = dump_value(1) # E: inference_failure
y = reveal_type(1) # E: inference_failure
assert_is_value(x, KnownValue(1))
assert_is_value(y, KnownValue(1))


class TestCallableGuards(TestNameCheckVisitorBase):
@assert_passes()
Expand Down
10 changes: 6 additions & 4 deletions pyanalyze/value.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,16 +301,18 @@ def assert_is_value(obj: object, value: Value, *, skip_annotated: bool = False)
pass


def dump_value(value: object) -> None:
def dump_value(value: T) -> T:
"""Print out the :class:`Value` representation of its argument.
Calling it will make pyanalyze print out an internal
representation of the argument's inferred value. Does nothing
at runtime. Use :func:`pyanalyze.extensions.reveal_type` for a
representation of the argument's inferred value. Use
:func:`pyanalyze.extensions.reveal_type` for a
more user-friendly representation.
At runtime this returns the argument unchanged.
"""
pass
return value


class AnySource(enum.Enum):
Expand Down

0 comments on commit 9879790

Please sign in to comment.