Refs https://github.com/astral-sh/ty/issues/544 ## Summary Takes a more incremental approach to PEP 613 type alias support (vs https://github.com/astral-sh/ruff/pull/20107). Instead of eagerly inferring the RHS of a PEP 613 type alias as a type expression, infer it as a value expression, just like we do for implicit type aliases, taking advantage of the same support for e.g. unions and other type special forms. The main reason I'm following this path instead of the one in https://github.com/astral-sh/ruff/pull/20107 is that we've realized that people do sometimes use PEP 613 type aliases as values, not just as types (because they are just a normal runtime assignment, unlike PEP 695 type aliases which create an opaque `TypeAliasType`). This PR doesn't yet provide full support for recursive type aliases (they don't panic, but they just fall back to `Unknown` at the recursion point). This is future work. ## Test Plan Added mdtests. Many new ecosystem diagnostics, mostly because we understand new types in lots of places. Conformance suite changes are correct. Performance regression is due to understanding lots of new types; nothing we do in this PR is inherently expensive.
3.3 KiB
lambda expression
No parameters
lambda expressions can be defined without any parameters.
reveal_type(lambda: 1) # revealed: () -> Unknown
# error: [unresolved-reference]
reveal_type(lambda: a) # revealed: () -> Unknown
With parameters
Unlike parameters in function definition, the parameters in a lambda expression cannot be
annotated.
reveal_type(lambda a: a) # revealed: (a) -> Unknown
reveal_type(lambda a, b: a + b) # revealed: (a, b) -> Unknown
But, it can have default values:
reveal_type(lambda a=1: a) # revealed: (a=Literal[1]) -> Unknown
reveal_type(lambda a, b=2: a) # revealed: (a, b=Literal[2]) -> Unknown
And, positional-only parameters:
reveal_type(lambda a, b, /, c: c) # revealed: (a, b, /, c) -> Unknown
And, keyword-only parameters:
reveal_type(lambda a, *, b=2, c: b) # revealed: (a, *, b=Literal[2], c) -> Unknown
And, variadic parameter:
reveal_type(lambda *args: args) # revealed: (*args) -> Unknown
And, keyword-varidic parameter:
reveal_type(lambda **kwargs: kwargs) # revealed: (**kwargs) -> Unknown
Mixing all of them together:
# revealed: (a, b, /, c=Literal[True], *args, *, d=Literal["default"], e=Literal[5], **kwargs) -> Unknown
reveal_type(lambda a, b, /, c=True, *args, d="default", e=5, **kwargs: None)
Parameter type
In addition to correctly inferring the lambda expression, the parameters should also be inferred
correctly.
Using a parameter with no default value:
lambda x: reveal_type(x) # revealed: Unknown
Using a parameter with default value:
lambda x=1: reveal_type(x) # revealed: Unknown | Literal[1]
Using a variadic parameter:
lambda *args: reveal_type(args) # revealed: tuple[Unknown, ...]
Using a keyword-variadic parameter:
lambda **kwargs: reveal_type(kwargs) # revealed: dict[str, Unknown]
Nested lambda expressions
Here, a lambda expression is used as the default value for a parameter in another lambda
expression.
reveal_type(lambda a=lambda x, y: 0: 2) # revealed: (a=(x, y) -> Unknown) -> Unknown
Assignment
This does not enumerate all combinations of parameter kinds as that should be covered by the subtype tests for callable types.
from typing import Callable
a1: Callable[[], None] = lambda: None
a2: Callable[[int], None] = lambda x: None
a3: Callable[[int, int], None] = lambda x, y, z=1: None
a4: Callable[[int, int], None] = lambda *args: None
# error: [invalid-assignment]
a5: Callable[[], None] = lambda x: None
# error: [invalid-assignment]
a6: Callable[[int], None] = lambda: None
Function-like behavior of lambdas
All lambda functions are instances of types.FunctionType and should have access to the same set
of attributes.
x = lambda y: y
reveal_type(x.__code__) # revealed: CodeType
reveal_type(x.__name__) # revealed: str
reveal_type(x.__defaults__) # revealed: tuple[Any, ...] | None
reveal_type(x.__annotations__) # revealed: dict[str, Any]
reveal_type(x.__dict__) # revealed: dict[str, Any]
reveal_type(x.__doc__) # revealed: str | None
reveal_type(x.__kwdefaults__) # revealed: dict[str, Any] | None
reveal_type(x.__module__) # revealed: str
reveal_type(x.__qualname__) # revealed: str