mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:16 +00:00

Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
## Summary Implement validation for `TypedDict` constructor calls and dictionary literal assignments, including support for `total=False` and proper field management. Also add support for `Required` and `NotRequired` type qualifiers in `TypedDict` classes, along with proper inheritance behavior and the `total=` parameter. Support both constructor calls and dict literal syntax part of https://github.com/astral-sh/ty/issues/154 ### Basic Required Field Validation ```py class Person(TypedDict): name: str age: int | None # Error: Missing required field 'name' in TypedDict `Person` constructor incomplete = Person(age=25) # Error: Invalid argument to key "name" with declared type `str` on TypedDict `Person` wrong_type = Person(name=123, age=25) # Error: Invalid key access on TypedDict `Person`: Unknown key "extra" extra_field = Person(name="Bob", age=25, extra=True) ``` <img width="773" height="191" alt="Screenshot 2025-08-07 at 17 59 22" src="https://github.com/user-attachments/assets/79076d98-e85f-4495-93d6-a731aa72a5c9" /> ### Support for `total=False` ```py class OptionalPerson(TypedDict, total=False): name: str age: int | None # All valid - all fields are optional with total=False charlie = OptionalPerson() david = OptionalPerson(name="David") emily = OptionalPerson(age=30) frank = OptionalPerson(name="Frank", age=25) # But type validation and extra fields still apply invalid_type = OptionalPerson(name=123) # Error: Invalid argument type invalid_extra = OptionalPerson(extra=True) # Error: Invalid key access ``` ### Dictionary Literal Validation ```py # Type checking works for both constructors and dict literals person: Person = {"name": "Alice", "age": 30} reveal_type(person["name"]) # revealed: str reveal_type(person["age"]) # revealed: int | None # Error: Invalid key access on TypedDict `Person`: Unknown key "non_existing" reveal_type(person["non_existing"]) # revealed: Unknown ``` ### `Required`, `NotRequired`, `total` ```python from typing import TypedDict from typing_extensions import Required, NotRequired class PartialUser(TypedDict, total=False): name: Required[str] # Required despite total=False age: int # Optional due to total=False email: NotRequired[str] # Explicitly optional (redundant) class User(TypedDict): name: Required[str] # Explicitly required (redundant) age: int # Required due to total=True bio: NotRequired[str] # Optional despite total=True # Valid constructions partial = PartialUser(name="Alice") # name required, age optional full = User(name="Bob", age=25) # name and age required, bio optional # Inheritance maintains original field requirements class Employee(PartialUser): department: str # Required (new field) # name: still Required (inherited) # age: still optional (inherited) emp = Employee(name="Charlie", department="Engineering") # ✅ Employee(department="Engineering") # ❌ e: Employee = {"age": 1} # ❌ ``` <img width="898" height="683" alt="Screenshot 2025-08-11 at 22 02 57" src="https://github.com/user-attachments/assets/4c1b18cd-cb2e-493a-a948-51589d121738" /> ## Implementation The implementation reuses existing validation logic done in https://github.com/astral-sh/ruff/pull/19782 ### ℹ️ Why I did NOT synthesize an `__init__` for `TypedDict`: `TypedDict` inherits `dict.__init__(self, *args, **kwargs)` that accepts all arguments. The type resolution system finds this inherited signature **before** looking for synthesized members. So `own_synthesized_member()` is never called because a signature already exists. To force synthesis, you'd have to override Python’s inheritance mechanism, which would break compatibility with the existing ecosystem. This is why I went with ad-hoc validation. IMO it's the only viable approach that respects Python’s inheritance semantics while providing the required validation. ### Refacto of `Field` **Before:** ```rust struct Field<'db> { declared_ty: Type<'db>, default_ty: Option<Type<'db>>, // NamedTuple and dataclass only init_only: bool, // dataclass only init: bool, // dataclass only is_required: Option<bool>, // TypedDict only } ``` **After:** ```rust struct Field<'db> { declared_ty: Type<'db>, kind: FieldKind<'db>, } enum FieldKind<'db> { NamedTuple { default_ty: Option<Type<'db>> }, Dataclass { default_ty: Option<Type<'db>>, init_only: bool, init: bool }, TypedDict { is_required: bool }, } ``` ## Test Plan Updated Markdown tests --------- Co-authored-by: David Peter <mail@david-peter.de>
59 lines
1.7 KiB
Markdown
59 lines
1.7 KiB
Markdown
# Environment variables
|
|
|
|
ty defines and respects the following environment variables:
|
|
|
|
### `TY_LOG`
|
|
|
|
If set, ty will use this value as the log level for its `--verbose` output.
|
|
Accepts any filter compatible with the `tracing_subscriber` crate.
|
|
|
|
For example:
|
|
|
|
- `TY_LOG=uv=debug` is the equivalent of `-vv` to the command line
|
|
- `TY_LOG=trace` will enable all trace-level logging.
|
|
|
|
See the [tracing documentation](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#example-syntax)
|
|
for more.
|
|
|
|
### `TY_LOG_PROFILE`
|
|
|
|
If set to `"1"` or `"true"`, ty will enable flamegraph profiling.
|
|
This creates a `tracing.folded` file that can be used to generate flame graphs
|
|
for performance analysis.
|
|
|
|
### `TY_MAX_PARALLELISM`
|
|
|
|
Specifies an upper limit for the number of tasks ty is allowed to run in parallel.
|
|
|
|
For example, how many files should be checked in parallel.
|
|
This isn't the same as a thread limit. ty may spawn additional threads
|
|
when necessary, e.g. to watch for file system changes or a dedicated UI thread.
|
|
|
|
## Externally-defined variables
|
|
|
|
ty also reads the following externally defined environment variables:
|
|
|
|
### `CONDA_DEFAULT_ENV`
|
|
|
|
Used to determine if an active Conda environment is the base environment or not.
|
|
|
|
### `CONDA_PREFIX`
|
|
|
|
Used to detect an activated Conda environment location.
|
|
If both `VIRTUAL_ENV` and `CONDA_PREFIX` are present, `VIRTUAL_ENV` will be preferred.
|
|
|
|
### `RAYON_NUM_THREADS`
|
|
|
|
Specifies an upper limit for the number of threads ty uses when performing work in parallel.
|
|
Equivalent to `TY_MAX_PARALLELISM`.
|
|
|
|
This is a standard Rayon environment variable.
|
|
|
|
### `VIRTUAL_ENV`
|
|
|
|
Used to detect an activated virtual environment.
|
|
|
|
### `XDG_CONFIG_HOME`
|
|
|
|
Path to user-level configuration directory on Unix systems.
|
|
|