Files
ruff/crates/ty_python_semantic/resources/mdtest/expression/lambda.md
Carl Meyer 6b7adb0537 [ty] support PEP 613 type aliases (#21394)
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.
2025-11-20 17:59:35 -08:00

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