diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b9ae4c..5a7608d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,103 @@ # Changelog +## 0.0.5 + +Released on 2025-12-20. + +### Bug fixes + +- Fix debug-mode server panic when a user typed a class definition by ensuring class arguments are visited in source order for semantic tokens ([#22063](https://github.com/astral-sh/ruff/pull/22063)) + +### LSP server + +- Classify docstrings in semantic tokens during syntax highlighting ([#22031](https://github.com/astral-sh/ruff/pull/22031)) + +### CLI + +- Add `--force-exclude` option ([#22076](https://github.com/astral-sh/ruff/pull/22076)) +- Only clear output between two successful checks ([#22078](https://github.com/astral-sh/ruff/pull/22078)) + +### Other changes + +- Add support for `dict(...)` calls in `TypedDict` contexts ([#22113](https://github.com/astral-sh/ruff/pull/22113)) +- Speedup bidirectional type-checking involving large unions by avoiding narrowing on non-generic calls ([#22102](https://github.com/astral-sh/ruff/pull/22102)) +- Simplify inferred types by avoiding storing multi-inference attempts ([#22062](https://github.com/astral-sh/ruff/pull/22062), [#22103](https://github.com/astral-sh/ruff/pull/22103)) +- Improve union builder performance ([#22048](https://github.com/astral-sh/ruff/pull/22048)) +- Only prefer declared types in non-covariant positions ([#22068](https://github.com/astral-sh/ruff/pull/22068)) +- Respect intersections in iterations ([#21965](https://github.com/astral-sh/ruff/pull/21965)) +- Sync vendored typeshed stubs ([#22091](https://github.com/astral-sh/ruff/pull/22091)). [Typeshed diff](https://github.com/python/typeshed/compare/ef2b90c67e5c668b91b3ae121baf00ee5165c30b...3c2dbb1fde8e8d1d59b10161c8bf5fd06c0011cd) +- Understand that the type of `X` on an enum class will be `int` if `X` is defined using `enum.nonmember` in the class definition ([#22025](https://github.com/astral-sh/ruff/pull/22025)) + +### Contributors + +- [@charliermarsh](https://github.com/charliermarsh) +- [@ibraheemdev](https://github.com/ibraheemdev) +- [@RasmusNygren](https://github.com/RasmusNygren) +- [@Hugo-Polloli](https://github.com/Hugo-Polloli) +- [@carljm](https://github.com/carljm) +- [@Gankra](https://github.com/Gankra) +- [@MichaReiser](https://github.com/MichaReiser) + +## 0.0.4 + +Released on 2025-12-18. + +### LSP server + +- Add support for attribute docstrings ([#22036](https://github.com/astral-sh/ruff/pull/22036)) +- Correctly encode multiline tokens for clients not supporting multiline tokens ([#22033](https://github.com/astral-sh/ruff/pull/22033)) +- Autocompletions: Don't suggest keyword statements when only expressions are valid ([#22002](https://github.com/astral-sh/ruff/pull/22002)) +- Fix goto-declaration on the right-hand side of `from module import submodule` ([#22042](https://github.com/astral-sh/ruff/pull/22042)) +- Fix some configuration panics in the LSP ([#22040](https://github.com/astral-sh/ruff/pull/22040)) +- Gracefully handle client requests that can't be deserialized ([#22051](https://github.com/astral-sh/ruff/pull/22051)) + +### Other changes + +- Improve performance for large match statements ([#22045](https://github.com/astral-sh/ruff/pull/22045)) +- Disable possibly-missing-imports by default ([#22041](https://github.com/astral-sh/ruff/pull/22041)) +- Implement disjointness for TypedDicts, significantly speeding up checking of code that uses pydantic ([#22044](https://github.com/astral-sh/ruff/pull/22044)) + +### Contributors + +- [@oconnor663](https://github.com/oconnor663) +- [@MichaReiser](https://github.com/MichaReiser) +- [@Gankra](https://github.com/Gankra) +- [@RasmusNygren](https://github.com/RasmusNygren) +- [@charliermarsh](https://github.com/charliermarsh) + +## 0.0.3 + +Released on 2025-12-17. + +### LSP server + +- Improve rendering of signatures in hovers ([#22007](https://github.com/astral-sh/ruff/pull/22007)) + +### Core type checking + +- Apply narrowing to `len` calls based on argument size ([#22026](https://github.com/astral-sh/ruff/pull/22026)) +- Don't add identical lower/upper bounds multiple times when inferring specializations ([#22030](https://github.com/astral-sh/ruff/pull/22030)) +- Improve `unsupported-base` and `invalid-super-argument` diagnostics to avoid extremely long lines when encountering verbose types ([#22022](https://github.com/astral-sh/ruff/pull/22022)) +- Improve disambiguation of types in many cases ([#22019](https://github.com/astral-sh/ruff/pull/22019)) +- Respect deferred values in keyword arguments etc. for `.pyi` files ([#22029](https://github.com/astral-sh/ruff/pull/22029)) +- Handle field specifier functions that accept `**kwargs` and recognize metaclass-based transformers as instances of `DataclassInstance` ([#22018](https://github.com/astral-sh/ruff/pull/22018)) + +### Contributors + +- [@charliermarsh](https://github.com/charliermarsh) +- [@sharkdp](https://github.com/sharkdp) +- [@Gankra](https://github.com/Gankra) +- [@zanieb](https://github.com/zanieb) +- [@AlexWaygood](https://github.com/AlexWaygood) +- [@dcreager](https://github.com/dcreager) + ## 0.0.2 Released on 2025-12-16. +This is the first Beta release of ty, which we're now ready to recommend to motivated users for +production use. See our [blog post](https://astral.sh/blog/ty) for more details. + ### LSP server - Improve display of completions to show actual insertion text ([#21988](https://github.com/astral-sh/ruff/pull/21988)) diff --git a/README.md b/README.md index fb243f8..665590c 100644 --- a/README.md +++ b/README.md @@ -47,9 +47,9 @@ To learn more about using ty, see the [documentation](https://docs.astral.sh/ty/ ## Installation -To install ty, see the [installation](./installation.md) documentation. +To install ty, see the [installation](https://docs.astral.sh/ty/installation/) documentation. -To add the ty language server to your editor, see the [editor integration](./editors.md) guide. +To add the ty language server to your editor, see the [editor integration](https://docs.astral.sh/ty/editors/) guide. ## Getting help @@ -73,6 +73,10 @@ See the +#### Why is ty doing \_\_\_\_\_? + +See our [typing FAQ](https://docs.astral.sh/ty/reference/typing-faq). + #### How do you pronounce ty? It's pronounced as "tee - why" ([`/tiː waɪ/`](https://en.wikipedia.org/wiki/Help:IPA/English#Key)) diff --git a/dist-workspace.toml b/dist-workspace.toml index 6089ec3..4870464 100644 --- a/dist-workspace.toml +++ b/dist-workspace.toml @@ -1,7 +1,7 @@ [workspace] members = ["cargo:./ruff"] packages = ["ty"] -version = "0.0.2" +version = "0.0.5" # Config for 'dist' [dist] diff --git a/docs/editors.md b/docs/editors.md index 2f28100..438dcf9 100644 --- a/docs/editors.md +++ b/docs/editors.md @@ -105,7 +105,7 @@ Starting with version 2025.3, PyCharm users can enable native ty support in the 1. Select which options should be enabled. -For more information, refer to [PyCharm documentation](https://www.jetbrains.com/help/pycharm/2025.3/lsp-tools.html#ty). +For more information, refer to [PyCharm documentation](https://www.jetbrains.com/help/pycharm/lsp-tools.html#ty). ## Other editors diff --git a/docs/installation.md b/docs/installation.md index c3194d1..c43793f 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -50,6 +50,72 @@ To update ty, use `uv tool upgrade`: uv tool upgrade ty ``` +### Installing with the standalone installer + +ty includes a standalone installer. + +=== "macOS and Linux" + + Use `curl` to download the script and execute it with `sh`: + + ```console + $ curl -LsSf https://astral.sh/ty/install.sh | sh + ``` + + If your system doesn't have `curl`, you can use `wget`: + + ```console + $ wget -qO- https://astral.sh/ty/install.sh | sh + ``` + + Request a specific version by including it in the URL: + + ```console + $ curl -LsSf https://astral.sh/ty/0.0.5/install.sh | sh + ``` + +=== "Windows" + + Use `irm` to download the script and execute it with `iex`: + + ```pwsh-session + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/ty/install.ps1 | iex" + ``` + + Changing the [execution policy](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.4#powershell-execution-policies) allows running a script from the internet. + + Request a specific version by including it in the URL: + + ```pwsh-session + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/ty/0.0.5/install.ps1 | iex" + ``` + +!!! tip + + The installation script may be inspected before use: + + === "macOS and Linux" + + ```console + $ curl -LsSf https://astral.sh/ty/install.sh | less + ``` + + === "Windows" + + ```pwsh-session + PS> powershell -c "irm https://astral.sh/ty/install.ps1 | more" + ``` + + Alternatively, the installer or binaries can be downloaded directly from [GitHub](#installing-from-github-releases). + +### Installing from GitHub Releases + +ty release artifacts can be downloaded directly from +[GitHub Releases](https://github.com/astral-sh/ty/releases). + +Each release page includes binaries for all supported platforms as well as instructions for using +the standalone installer via `github.com` instead of `astral.sh`. + ### Installing globally with pipx Install ty globally with pipx: @@ -83,7 +149,7 @@ COPY --from=ghcr.io/astral-sh/ty:latest /ty /bin/ The following tags are available: - `ghcr.io/astral-sh/ty:latest` -- `ghcr.io/astral-sh/ty:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/ty:0.0.2` +- `ghcr.io/astral-sh/ty:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/ty:0.0.5` - `ghcr.io/astral-sh/ty:{major}.{minor}`, e.g., `ghcr.io/astral-sh/ty:0.0` (the latest patch version) diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 97a9a1c..d701649 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -56,6 +56,7 @@ over all configuration files.

--exit-zero

Always use exit code 0, even when there are error-level diagnostics

--extra-search-path path

Additional path to use as a module-resolution source (can be passed multiple times).

This is an advanced option that should usually only be used for first-party or third-party modules that are not installed into your Python environment in a conventional way. Use --python to point ty to your Python environment if it is in an unusual location.

+
--force-exclude

Enforce exclusions, even for paths passed to ty directly on the command-line. Use --no-force-exclude to disable

--help, -h

Print help (see a summary with '-h')

--ignore rule

Disables the rule. Can be specified multiple times.

--no-progress

Hide all progress outputs.

diff --git a/docs/reference/editor-settings.md b/docs/reference/editor-settings.md index 2b6d0a2..659bb0f 100644 --- a/docs/reference/editor-settings.md +++ b/docs/reference/editor-settings.md @@ -66,12 +66,16 @@ ______________________________________________________________________ Determines the scope of the diagnostics reported by the language server. +Setting this to `off` is useful if you want to use ty exclusively for the language server features +like code completion, hover, go to definition, etc. + +- `off`: Diagnostics are disabled. - `openFilesOnly`: Diagnostics are reported only for files that are currently open in the editor. - `workspace`: Diagnostics are reported for all files in the workspace. **Default value**: `"openFilesOnly"` -**Type**: `"workspace" | "openFilesOnly"` +**Type**: `"off" | "workspace" | "openFilesOnly"` **Example usage**: diff --git a/docs/reference/rules.md b/docs/reference/rules.md index 8b35be5..db57225 100644 --- a/docs/reference/rules.md +++ b/docs/reference/rules.md @@ -2,10 +2,51 @@ # Rules +## `ambiguous-protocol-member` + + +Default level: warn · +Added in 0.0.1-alpha.20 · +Related issues · +View source + + + +**What it does** + +Checks for protocol classes with members that will lead to ambiguous interfaces. + +**Why is this bad?** + +Assigning to an undeclared variable in a protocol class leads to an ambiguous +interface which may lead to the type checker inferring unexpected things. It's +recommended to ensure that all members of a protocol class are explicitly declared. + +**Examples** + + +```py +from typing import Protocol + +class BaseProto(Protocol): + a: int # fine (explicitly declared as `int`) + def method_member(self) -> int: ... # fine: a method definition using `def` is considered a declaration + c = "some variable" # error: no explicit declaration, leading to ambiguity + b = method_member # error: no explicit declaration, leading to ambiguity + + # error: this creates implicit assignments of `d` and `e` in the protocol class body. + # Were they really meant to be considered protocol members? + for d, e in enumerate(range(42)): + pass + +class SubProto(BaseProto, Protocol): + a = 42 # fine (declared in superclass) +``` + ## `byte-string-type-annotation` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · View source @@ -36,7 +77,7 @@ def test(): -> "int": ## `call-non-callable` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · View source @@ -60,7 +101,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. ## `conflicting-argument-forms` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · View source @@ -92,7 +133,7 @@ f(int) # error ## `conflicting-declarations` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · View source @@ -123,7 +164,7 @@ a = 1 ## `conflicting-metaclass` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · View source @@ -155,7 +196,7 @@ class C(A, B): ... ## `cyclic-class-definition` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · View source @@ -187,7 +228,7 @@ class B(A): ... ## `cyclic-type-alias-definition` -Default level: error · +Default level: error · Preview (since 1.0.0) · Related issues · View source @@ -212,13 +253,69 @@ type A = B type B = A ``` +## `deprecated` + + +Default level: warn · +Added in 0.0.1-alpha.16 · +Related issues · +View source + + + +**What it does** + +Checks for uses of deprecated items + +**Why is this bad?** + +Deprecated items should no longer be used. + +**Examples** + +```python +@warnings.deprecated("use new_func instead") +def old_func(): ... + +old_func() # emits [deprecated] diagnostic +``` + +## `division-by-zero` + + +Default level: ignore · +Preview (since 0.0.1-alpha.1) · +Related issues · +View source + + + +**What it does** + +It detects division by zero. + +**Why is this bad?** + +Dividing by zero raises a `ZeroDivisionError` at runtime. + +**Rule status** + +This rule is currently disabled by default because of the number of +false positives it can produce. + +**Examples** + +```python +5 / 0 +``` + ## `duplicate-base` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -242,10 +339,10 @@ class B(A, A): ... ## `duplicate-kw-only` -Default level: error · +Default level: error · Added in 0.0.1-alpha.12 · Related issues · -View source +View source @@ -280,7 +377,7 @@ class A: # Crash at runtime ## `escape-character-in-forward-annotation` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · View source @@ -292,7 +389,7 @@ TODO #14889 ## `fstring-type-annotation` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · View source @@ -320,10 +417,41 @@ def test(): -> "int": ... ``` +## `ignore-comment-unknown-rule` + + +Default level: warn · +Added in 0.0.1-alpha.1 · +Related issues · +View source + + + +**What it does** + +Checks for `ty: ignore[code]` where `code` isn't a known lint rule. + +**Why is this bad?** + +A `ty: ignore[code]` directive with a `code` that doesn't match +any known rule will not suppress any type errors, and is probably a mistake. + +**Examples** + +```py +a = 20 / 0 # ty: ignore[division-by-zer] +``` + +Use instead: + +```py +a = 20 / 0 # ty: ignore[division-by-zero] +``` + ## `implicit-concatenated-string-type-annotation` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · View source @@ -354,10 +482,10 @@ def test(): -> "Literal[5]": ## `inconsistent-mro` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -384,10 +512,10 @@ class C(A, B): ... ## `index-out-of-bounds` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -410,10 +538,10 @@ t[3] # IndexError: tuple index out of range ## `instance-layout-conflict` -Default level: error · +Default level: error · Added in 0.0.1-alpha.12 · Related issues · -View source +View source @@ -499,10 +627,10 @@ an atypical memory layout. ## `invalid-argument-type` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -526,10 +654,10 @@ func("foo") # error: [invalid-argument-type] ## `invalid-assignment` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -554,10 +682,10 @@ a: int = '' ## `invalid-attribute-access` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -588,10 +716,10 @@ C.instance_var = 3 # error: Cannot assign to instance variable ## `invalid-await` -Default level: error · +Default level: error · Added in 0.0.1-alpha.19 · Related issues · -View source +View source @@ -624,10 +752,10 @@ asyncio.run(main()) ## `invalid-base` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -648,10 +776,10 @@ class A(42): ... # error: [invalid-base] ## `invalid-context-manager` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -675,10 +803,10 @@ with 1: ## `invalid-declaration` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -704,10 +832,10 @@ a: str ## `invalid-exception-caught` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -748,10 +876,10 @@ except ZeroDivisionError: ## `invalid-explicit-override` -Default level: error · +Default level: error · Added in 0.0.1-alpha.28 · Related issues · -View source +View source @@ -790,10 +918,10 @@ class D(A): ## `invalid-frozen-dataclass-subclass` -Default level: error · +Default level: error · Added in 0.0.1-alpha.35 · Related issues · -View source +View source @@ -834,10 +962,10 @@ class NonFrozenChild(FrozenBase): # Error raised here ## `invalid-generic-class` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -869,13 +997,43 @@ class D(Generic[U, T]): ... - [Typing spec: Generics](https://typing.python.org/en/latest/spec/generics.html#introduction) +## `invalid-ignore-comment` + + +Default level: warn · +Added in 0.0.1-alpha.1 · +Related issues · +View source + + + +**What it does** + +Checks for `type: ignore` and `ty: ignore` comments that are syntactically incorrect. + +**Why is this bad?** + +A syntactically incorrect ignore comment is probably a mistake and is useless. + +**Examples** + +```py +a = 20 / 0 # type: ignoree +``` + +Use instead: + +```py +a = 20 / 0 # type: ignore +``` + ## `invalid-key` -Default level: error · +Default level: error · Added in 0.0.1-alpha.17 · Related issues · -View source +View source @@ -911,10 +1069,10 @@ carol = Person(name="Carol", age=25) # typo! ## `invalid-legacy-type-variable` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -946,10 +1104,10 @@ def f(t: TypeVar("U")): ... ## `invalid-metaclass` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -980,10 +1138,10 @@ class B(metaclass=f): ... ## `invalid-method-override` -Default level: error · +Default level: error · Added in 0.0.1-alpha.20 · Related issues · -View source +View source @@ -1087,10 +1245,10 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule. ## `invalid-named-tuple` -Default level: error · +Default level: error · Added in 0.0.1-alpha.19 · Related issues · -View source +View source @@ -1141,10 +1299,10 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict ## `invalid-newtype` -Default level: error · +Default level: error · Preview (since 1.0.0) · Related issues · -View source +View source @@ -1171,10 +1329,10 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType` ## `invalid-overload` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1221,10 +1379,10 @@ def foo(x: int) -> int: ... ## `invalid-parameter-default` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1247,10 +1405,10 @@ def f(a: int = ''): ... ## `invalid-paramspec` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1278,10 +1436,10 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig ## `invalid-protocol` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1312,10 +1470,10 @@ TypeError: Protocols can only inherit from other protocols, got ## `invalid-raise` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1361,10 +1519,10 @@ def g(): ## `invalid-return-type` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1386,10 +1544,10 @@ def func() -> int: ## `invalid-super-argument` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1432,7 +1590,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)` ## `invalid-syntax-in-forward-annotation` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · View source @@ -1444,10 +1602,10 @@ TODO #14889 ## `invalid-type-alias-type` -Default level: error · +Default level: error · Added in 0.0.1-alpha.6 · Related issues · -View source +View source @@ -1471,10 +1629,10 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus ## `invalid-type-arguments` -Default level: error · +Default level: error · Added in 0.0.1-alpha.29 · Related issues · -View source +View source @@ -1518,10 +1676,10 @@ Bar[int] # error: too few arguments ## `invalid-type-checking-constant` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1548,10 +1706,10 @@ TYPE_CHECKING = '' ## `invalid-type-form` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1578,10 +1736,10 @@ b: Annotated[int] # `Annotated` expects at least two arguments ## `invalid-type-guard-call` -Default level: error · +Default level: error · Added in 0.0.1-alpha.11 · Related issues · -View source +View source @@ -1612,10 +1770,10 @@ f(10) # Error ## `invalid-type-guard-definition` -Default level: error · +Default level: error · Added in 0.0.1-alpha.11 · Related issues · -View source +View source @@ -1646,10 +1804,10 @@ class C: ## `invalid-type-variable-constraints` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1681,10 +1839,10 @@ T = TypeVar('T', bound=str) # valid bound TypeVar ## `missing-argument` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1706,10 +1864,10 @@ func() # TypeError: func() missing 1 required positional argument: 'x' ## `missing-typed-dict-key` -Default level: error · +Default level: error · Added in 0.0.1-alpha.20 · Related issues · -View source +View source @@ -1739,10 +1897,10 @@ alice["age"] # KeyError ## `no-matching-overload` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1768,10 +1926,10 @@ func("string") # error: [no-matching-overload] ## `non-subscriptable` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1792,10 +1950,10 @@ Subscripting an object that does not support it will raise a `TypeError` at runt ## `not-iterable` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1818,10 +1976,10 @@ for i in 34: # TypeError: 'int' object is not iterable ## `override-of-final-method` -Default level: error · +Default level: error · Added in 0.0.1-alpha.29 · Related issues · -View source +View source @@ -1851,10 +2009,10 @@ class B(A): ## `parameter-already-assigned` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1878,10 +2036,10 @@ f(1, x=2) # Error raised here ## `positional-only-parameter-as-kwarg` -Default level: error · +Default level: error · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -1902,10 +2060,140 @@ def f(x: int, /) -> int: ... f(x=1) # Error raised here ``` +## `possibly-missing-attribute` + + +Default level: warn · +Added in 0.0.1-alpha.22 · +Related issues · +View source + + + +**What it does** + +Checks for possibly missing attributes. + +**Why is this bad?** + +Attempting to access a missing attribute will raise an `AttributeError` at runtime. + +**Examples** + +```python +class A: + if b: + c = 0 + +A.c # AttributeError: type object 'A' has no attribute 'c' +``` + +## `possibly-missing-implicit-call` + + +Default level: warn · +Added in 0.0.1-alpha.22 · +Related issues · +View source + + + +**What it does** + +Checks for implicit calls to possibly missing methods. + +**Why is this bad?** + +Expressions such as `x[y]` and `x * y` call methods +under the hood (`__getitem__` and `__mul__` respectively). +Calling a missing method will raise an `AttributeError` at runtime. + +**Examples** + +```python +import datetime + +class A: + if datetime.date.today().weekday() != 6: + def __getitem__(self, v): ... + +A()[0] # TypeError: 'A' object is not subscriptable +``` + +## `possibly-missing-import` + + +Default level: ignore · +Added in 0.0.1-alpha.22 · +Related issues · +View source + + + +**What it does** + +Checks for imports of symbols that may be missing. + +**Why is this bad?** + +Importing a missing module or name will raise a `ModuleNotFoundError` +or `ImportError` at runtime. + +**Rule status** + +This rule is currently disabled by default because of the number of +false positives it can produce. + +**Examples** + +```python +# module.py +import datetime + +if datetime.date.today().weekday() != 6: + a = 1 + +# main.py +from module import a # ImportError: cannot import name 'a' from 'module' +``` + +## `possibly-unresolved-reference` + + +Default level: ignore · +Added in 0.0.1-alpha.1 · +Related issues · +View source + + + +**What it does** + +Checks for references to names that are possibly not defined. + +**Why is this bad?** + +Using an undefined variable will raise a `NameError` at runtime. + +**Rule status** + +This rule is currently disabled by default because of the number of +false positives it can produce. + +**Example** + + +```python +for i in range(0): + x = i + +print(x) # NameError: name 'x' is not defined +``` + ## `raw-string-type-annotation` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · View source @@ -1933,13 +2221,40 @@ def test(): -> "int": ... ``` +## `redundant-cast` + + +Default level: warn · +Added in 0.0.1-alpha.1 · +Related issues · +View source + + + +**What it does** + +Detects redundant `cast` calls where the value already has the target type. + +**Why is this bad?** + +These casts have no effect and can be removed. + +**Example** + +```python +def f() -> int: + return 10 + +cast(int, f()) # Redundant +``` + ## `static-assert-error` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1966,10 +2281,10 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr ## `subclass-of-final-class` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1995,10 +2310,10 @@ class B(A): ... # Error raised here ## `super-call-in-named-tuple-method` -Default level: error · +Default level: error · Preview (since 0.0.1-alpha.30) · Related issues · -View source +View source @@ -2029,10 +2344,10 @@ class F(NamedTuple): ## `too-many-positional-arguments` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2056,10 +2371,10 @@ f("foo") # Error raised here ## `type-assertion-failure` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2084,10 +2399,10 @@ def _(x: int): ## `unavailable-implicit-super-arguments` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2127,13 +2442,37 @@ class A: - [Python documentation: super()](https://docs.python.org/3/library/functions.html#super) +## `undefined-reveal` + + +Default level: warn · +Added in 0.0.1-alpha.1 · +Related issues · +View source + + + +**What it does** + +Checks for calls to `reveal_type` without importing it. + +**Why is this bad?** + +Using `reveal_type` without importing it will raise a `NameError` at runtime. + +**Examples** + +```python +reveal_type(1) # NameError: name 'reveal_type' is not defined +``` + ## `unknown-argument` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2157,10 +2496,10 @@ f(x=1, y=2) # Error raised here ## `unresolved-attribute` -Default level: error · +Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2182,425 +2521,13 @@ class A: ... A().foo # AttributeError: 'A' object has no attribute 'foo' ``` -## `unresolved-import` - - -Default level: error · -Added in 0.0.1-alpha.1 · -Related issues · -View source - - - -**What it does** - -Checks for import statements for which the module cannot be resolved. - -**Why is this bad?** - -Importing a module that cannot be resolved will raise a `ModuleNotFoundError` -at runtime. - -**Examples** - -```python -import foo # ModuleNotFoundError: No module named 'foo' -``` - -## `unresolved-reference` - - -Default level: error · -Added in 0.0.1-alpha.1 · -Related issues · -View source - - - -**What it does** - -Checks for references to names that are not defined. - -**Why is this bad?** - -Using an undefined variable will raise a `NameError` at runtime. - -**Example** - - -```python -print(x) # NameError: name 'x' is not defined -``` - -## `unsupported-bool-conversion` - - -Default level: error · -Added in 0.0.1-alpha.1 · -Related issues · -View source - - - -**What it does** - -Checks for bool conversions where the object doesn't correctly implement `__bool__`. - -**Why is this bad?** - -If an exception is raised when you attempt to evaluate the truthiness of an object, -using the object in a boolean context will fail at runtime. - -**Examples** - - -```python -class NotBoolable: - __bool__ = None - -b1 = NotBoolable() -b2 = NotBoolable() - -if b1: # exception raised here - pass - -b1 and b2 # exception raised here -not b1 # exception raised here -b1 < b2 < b1 # exception raised here -``` - -## `unsupported-operator` - - -Default level: error · -Added in 0.0.1-alpha.1 · -Related issues · -View source - - - -**What it does** - -Checks for binary expressions, comparisons, and unary expressions where -the operands don't support the operator. - -**Why is this bad?** - -Attempting to use an unsupported operator will raise a `TypeError` at -runtime. - -**Examples** - -```python -class A: ... - -A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' -``` - -## `zero-stepsize-in-slice` - - -Default level: error · -Added in 0.0.1-alpha.1 · -Related issues · -View source - - - -**What it does** - -Checks for step size 0 in slices. - -**Why is this bad?** - -A slice with a step size of zero will raise a `ValueError` at runtime. - -**Examples** - -```python -l = list(range(10)) -l[1:10:0] # ValueError: slice step cannot be zero -``` - -## `ambiguous-protocol-member` - - -Default level: warn · -Added in 0.0.1-alpha.20 · -Related issues · -View source - - - -**What it does** - -Checks for protocol classes with members that will lead to ambiguous interfaces. - -**Why is this bad?** - -Assigning to an undeclared variable in a protocol class leads to an ambiguous -interface which may lead to the type checker inferring unexpected things. It's -recommended to ensure that all members of a protocol class are explicitly declared. - -**Examples** - - -```py -from typing import Protocol - -class BaseProto(Protocol): - a: int # fine (explicitly declared as `int`) - def method_member(self) -> int: ... # fine: a method definition using `def` is considered a declaration - c = "some variable" # error: no explicit declaration, leading to ambiguity - b = method_member # error: no explicit declaration, leading to ambiguity - - # error: this creates implicit assignments of `d` and `e` in the protocol class body. - # Were they really meant to be considered protocol members? - for d, e in enumerate(range(42)): - pass - -class SubProto(BaseProto, Protocol): - a = 42 # fine (declared in superclass) -``` - -## `deprecated` - - -Default level: warn · -Added in 0.0.1-alpha.16 · -Related issues · -View source - - - -**What it does** - -Checks for uses of deprecated items - -**Why is this bad?** - -Deprecated items should no longer be used. - -**Examples** - -```python -@warnings.deprecated("use new_func instead") -def old_func(): ... - -old_func() # emits [deprecated] diagnostic -``` - -## `ignore-comment-unknown-rule` - - -Default level: warn · -Added in 0.0.1-alpha.1 · -Related issues · -View source - - - -**What it does** - -Checks for `ty: ignore[code]` where `code` isn't a known lint rule. - -**Why is this bad?** - -A `ty: ignore[code]` directive with a `code` that doesn't match -any known rule will not suppress any type errors, and is probably a mistake. - -**Examples** - -```py -a = 20 / 0 # ty: ignore[division-by-zer] -``` - -Use instead: - -```py -a = 20 / 0 # ty: ignore[division-by-zero] -``` - -## `invalid-ignore-comment` - - -Default level: warn · -Added in 0.0.1-alpha.1 · -Related issues · -View source - - - -**What it does** - -Checks for `type: ignore` and `ty: ignore` comments that are syntactically incorrect. - -**Why is this bad?** - -A syntactically incorrect ignore comment is probably a mistake and is useless. - -**Examples** - -```py -a = 20 / 0 # type: ignoree -``` - -Use instead: - -```py -a = 20 / 0 # type: ignore -``` - -## `possibly-missing-attribute` - - -Default level: warn · -Added in 0.0.1-alpha.22 · -Related issues · -View source - - - -**What it does** - -Checks for possibly missing attributes. - -**Why is this bad?** - -Attempting to access a missing attribute will raise an `AttributeError` at runtime. - -**Examples** - -```python -class A: - if b: - c = 0 - -A.c # AttributeError: type object 'A' has no attribute 'c' -``` - -## `possibly-missing-implicit-call` - - -Default level: warn · -Added in 0.0.1-alpha.22 · -Related issues · -View source - - - -**What it does** - -Checks for implicit calls to possibly missing methods. - -**Why is this bad?** - -Expressions such as `x[y]` and `x * y` call methods -under the hood (`__getitem__` and `__mul__` respectively). -Calling a missing method will raise an `AttributeError` at runtime. - -**Examples** - -```python -import datetime - -class A: - if datetime.date.today().weekday() != 6: - def __getitem__(self, v): ... - -A()[0] # TypeError: 'A' object is not subscriptable -``` - -## `possibly-missing-import` - - -Default level: warn · -Added in 0.0.1-alpha.22 · -Related issues · -View source - - - -**What it does** - -Checks for imports of symbols that may be missing. - -**Why is this bad?** - -Importing a missing module or name will raise a `ModuleNotFoundError` -or `ImportError` at runtime. - -**Examples** - -```python -# module.py -import datetime - -if datetime.date.today().weekday() != 6: - a = 1 - -# main.py -from module import a # ImportError: cannot import name 'a' from 'module' -``` - -## `redundant-cast` - - -Default level: warn · -Added in 0.0.1-alpha.1 · -Related issues · -View source - - - -**What it does** - -Detects redundant `cast` calls where the value already has the target type. - -**Why is this bad?** - -These casts have no effect and can be removed. - -**Example** - -```python -def f() -> int: - return 10 - -cast(int, f()) # Redundant -``` - -## `undefined-reveal` - - -Default level: warn · -Added in 0.0.1-alpha.1 · -Related issues · -View source - - - -**What it does** - -Checks for calls to `reveal_type` without importing it. - -**Why is this bad?** - -Using `reveal_type` without importing it will raise a `NameError` at runtime. - -**Examples** - -```python -reveal_type(1) # NameError: name 'reveal_type' is not defined -``` - ## `unresolved-global` -Default level: warn · +Default level: warn · Added in 0.0.1-alpha.15 · Related issues · -View source +View source @@ -2652,13 +2579,63 @@ def g(): print(x) ``` +## `unresolved-import` + + +Default level: error · +Added in 0.0.1-alpha.1 · +Related issues · +View source + + + +**What it does** + +Checks for import statements for which the module cannot be resolved. + +**Why is this bad?** + +Importing a module that cannot be resolved will raise a `ModuleNotFoundError` +at runtime. + +**Examples** + +```python +import foo # ModuleNotFoundError: No module named 'foo' +``` + +## `unresolved-reference` + + +Default level: error · +Added in 0.0.1-alpha.1 · +Related issues · +View source + + + +**What it does** + +Checks for references to names that are not defined. + +**Why is this bad?** + +Using an undefined variable will raise a `NameError` at runtime. + +**Example** + + +```python +print(x) # NameError: name 'x' is not defined +``` + ## `unsupported-base` -Default level: warn · +Default level: warn · Added in 0.0.1-alpha.7 · Related issues · -View source +View source @@ -2691,13 +2668,109 @@ class D(C): ... # error: [unsupported-base] [method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order +## `unsupported-bool-conversion` + + +Default level: error · +Added in 0.0.1-alpha.1 · +Related issues · +View source + + + +**What it does** + +Checks for bool conversions where the object doesn't correctly implement `__bool__`. + +**Why is this bad?** + +If an exception is raised when you attempt to evaluate the truthiness of an object, +using the object in a boolean context will fail at runtime. + +**Examples** + + +```python +class NotBoolable: + __bool__ = None + +b1 = NotBoolable() +b2 = NotBoolable() + +if b1: # exception raised here + pass + +b1 and b2 # exception raised here +not b1 # exception raised here +b1 < b2 < b1 # exception raised here +``` + +## `unsupported-operator` + + +Default level: error · +Added in 0.0.1-alpha.1 · +Related issues · +View source + + + +**What it does** + +Checks for binary expressions, comparisons, and unary expressions where +the operands don't support the operator. + +**Why is this bad?** + +Attempting to use an unsupported operator will raise a `TypeError` at +runtime. + +**Examples** + +```python +class A: ... + +A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' +``` + +## `unused-ignore-comment` + + +Default level: ignore · +Added in 0.0.1-alpha.1 · +Related issues · +View source + + + +**What it does** + +Checks for `type: ignore` or `ty: ignore` directives that are no longer applicable. + +**Why is this bad?** + +A `type: ignore` directive that no longer matches any diagnostic violations is likely +included by mistake, and should be removed to avoid confusion. + +**Examples** + +```py +a = 20 / 2 # ty: ignore[division-by-zero] +``` + +Use instead: + +```py +a = 20 / 2 +``` + ## `useless-overload-body` -Default level: warn · +Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2754,86 +2827,28 @@ def foo(x: int | str) -> int | str: - [Python documentation: `@overload`](https://docs.python.org/3/library/typing.html#typing.overload) -## `division-by-zero` +## `zero-stepsize-in-slice` -Default level: ignore · -Preview (since 0.0.1-alpha.1) · -Related issues · -View source +Default level: error · +Added in 0.0.1-alpha.1 · +Related issues · +View source **What it does** -It detects division by zero. +Checks for step size 0 in slices. **Why is this bad?** -Dividing by zero raises a `ZeroDivisionError` at runtime. +A slice with a step size of zero will raise a `ValueError` at runtime. **Examples** ```python -5 / 0 -``` - -## `possibly-unresolved-reference` - - -Default level: ignore · -Added in 0.0.1-alpha.1 · -Related issues · -View source - - - -**What it does** - -Checks for references to names that are possibly not defined. - -**Why is this bad?** - -Using an undefined variable will raise a `NameError` at runtime. - -**Example** - - -```python -for i in range(0): - x = i - -print(x) # NameError: name 'x' is not defined -``` - -## `unused-ignore-comment` - - -Default level: ignore · -Added in 0.0.1-alpha.1 · -Related issues · -View source - - - -**What it does** - -Checks for `type: ignore` or `ty: ignore` directives that are no longer applicable. - -**Why is this bad?** - -A `type: ignore` directive that no longer matches any diagnostic violations is likely -included by mistake, and should be removed to avoid confusion. - -**Examples** - -```py -a = 20 / 2 # ty: ignore[division-by-zero] -``` - -Use instead: - -```py -a = 20 / 2 +l = list(range(10)) +l[1:10:0] # ValueError: slice step cannot be zero ``` diff --git a/docs/reference/typing-faq.md b/docs/reference/typing-faq.md new file mode 100644 index 0000000..f310e7d --- /dev/null +++ b/docs/reference/typing-faq.md @@ -0,0 +1,272 @@ +# Typing FAQ + +This page answers some commonly asked questions about ty and Python's type system. + +## Why does ty report an error on my code? + +Check the [documentation](https://docs.astral.sh/ty/reference/rules/) for the specific error code +you are seeing; it may explain the problem. + +## What is the `Unknown` type and when does it appear? + +`Unknown` is ty's way of representing a type that could not be fully inferred. It behaves the same +way as `Any`, but appears implicitly, rather than through an explicit `Any` annotation: + +```py +from missing_module import MissingClass # error: unresolved-import + +reveal_type(MissingClass) # Unknown +``` + +ty also uses unions with `Unknown` to maintain the +[gradual guarantee](../features/type-system.md#gradual-guarantee), which helps avoid false positive +errors in untyped code while still providing useful type information where possible. + +For example, consider the following untyped `Message` class (which could come from a third-party +dependency that you have no control over). ty treats the `data` attribute as having type +`Unknown | None`, since there is no type annotation that restricts it further. The `Unknown` in the +union allows ty to avoid raising errors on the `msg.data = …` assignment. On the other hand, the +`None` in the union reflects the fact that `data` *could* possibly be `None`, and requires code that +uses `msg.data` to handle that case explicitly. + +```py +class Message: + data = None + + def __init__(self, title): + self.title = title + + +def receive(msg: Message): + reveal_type(msg.data) # Unknown | None + + +msg = Message("Favorite color") +msg.data = {"color": "blue"} +``` + +([Full example in the playground](https://play.ty.dev/862941a8-a3f6-4818-9ea1-d9d59b0bd2fa)) + +## Why does ty show `int | float` when I annotate something as `float`? + +The [Python typing specification](https://typing.python.org/en/latest/spec/special-types.html) +includes a special rule for numeric types where an `int` can be used wherever a `float` is expected: + +```py +def circle_area(radius: float) -> float: + return 3.14 * radius * radius + +circle_area(2) # OK: int is allowed where float is expected +``` + +This rule is a special case, since `int` is not actually a subclass of `float`. To support this, ty +treats `float` annotations as meaning `int | float`. Unlike some other type checkers, ty makes this +behavior explicit in type hints and error messages. For example, if you +[hover over the `radius` parameter](https://play.ty.dev/fdc144c6-031c-4af9-b520-a4c6ccde9261), ty +will show `int | float`. + +A similar rule applies to `complex`, which is treated as `int | float | complex`. + +!!! info + + These special rules for `float` and `complex` exist for a reason. In almost all cases, you + probably want to accept both `int` and `float` when you annotate something as `float`. + If you really need to accept *only* `float` and not `int`, you can use ty's `JustFloat` + type. At the time of writing, this import needs to be guarded by a `TYPE_CHECKING` block: + + ```py + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from ty_extensions import JustFloat + else: + JustFloat = float + + def only_actual_floats_allowed(f: JustFloat) -> None: ... + + only_actual_floats_allowed(1.0) # OK + only_actual_floats_allowed(1) # error: invalid-argument-type + ``` + + ([Full example in the playground](https://play.ty.dev/fb034780-3ba7-4c6a-9449-5b0f44128bab)) + + If you need this for `complex`, you can use `ty_extensions.JustComplex` in a similar way. + +## Why does ty say `Callable` has no attribute `__name__`? + +When you access `__name__`, `__qualname__`, `__module__`, or `__doc__` on a value typed as `Callable`, +ty reports an `unresolved-attribute` error. This is because not all callables have these attributes. +Functions do (including lambdas), but other callable objects do not. The `FileUpload` class below, for +example, is callable, but instances of `FileUpload` do not have a `__name__` attribute. Passing a +`FileUpload` instance to `retry` would lead to an `AttributeError` at runtime. + +```py +from typing import Callable + +def retry(times: int, operation: Callable[[], bool]) -> bool: + for i in range(times): + # WRONG: `operation` does not necessarily have a `__name__` attribute + print(f"Calling {operation.__name__}, attempt {i + 1} of {times}") + if operation(): + return True + return False + +class FileUpload: + def __init__(self, name: str) -> None: + # … + + def __call__(self) -> bool: + # … + +retry(3, FileUpload("image.png")) +``` + +To fix this, you could use `getattr` with a fall back to a default name when the +attribute is not present (or use a `hasattr(…, "__name__")` check if you access +it multiple times): + +```py +name = getattr(operation, "__name__", "operation") +``` + +Alternatively, you could use an `isinstance(…, types.FunctionType)` check to narrow the type of +`operation` to something that definitely has a `__name__` attribute: + +```py +if isinstance(operation, FunctionType): + print(f"Calling {operation.__name__}, attempt {i + 1} of {times}") +else: + print(f"Calling operation, attempt {i + 1} of {times}") +``` + +You can try various approaches in [this playground example](https://play.ty.dev/f6f7f35a-47c3-423d-be8d-33d03c61d40c). +See also [this discussion](https://github.com/astral-sh/ty/issues/1495) for some plans to improve +the developer experience around this in the future. + +!!! info + + ty has first-class support for intersection types. If you only want to accept function-like + callables, you could define `FunctionLikeCallable` as an intersection of `Callable` and + `types.FunctionType`: + + ```py + from typing import Callable, TYPE_CHECKING + from types import FunctionType + + if TYPE_CHECKING: + from ty_extensions import Intersection + + type FunctionLikeCallable[**P, R] = Intersection[Callable[P, R], FunctionType] + else: + FunctionLikeCallable = Callable + + + def retry(times: int, operation: FunctionLikeCallable[[], bool]) -> bool: + ... + ``` + + You can check out the full example [here](https://play.ty.dev/7a1ea4ab-04e1-4271-adf5-ddc3a5d2fcfd), + which demonstrates that `FileUpload` instances are no longer accepted by `retry`. + +## Does ty have a strict mode? + +Not yet. A stricter inference mode is tracked in +[this issue](https://github.com/astral-sh/ty/issues/1240). In the meantime, you can consider using Ruff's +[`flake8-annotations` rules](https://docs.astral.sh/ruff/rules/#flake8-annotations-ann) to enforce +more explicit type annotations in your code. + +## Why can't ty resolve my imports? + +Import resolution issues are often caused by a missing or incorrect environment configuration. When +ty reports *"Cannot resolve imported module …"*, check the following: + +1. **Virtual environment**: Make sure your virtual environment is discoverable. ty looks for an + active virtual environment via `VIRTUAL_ENV` or a `.venv` directory in your project root. See the + [module discovery](../modules.md#python-environment) documentation for more details. + +1. **Project structure**: If your source code is not in the project root or `src/` directory, + configure [`environment.root`](./configuration.md#root) in your `pyproject.toml`: + + ```toml + [tool.ty.environment] + root = ["./app"] + ``` + +1. **Third-party packages**: Ensure dependencies are installed in your virtual environment. Run ty + with `-v` to see the search paths being used. + +1. **Compiled extensions**: ty requires `.py` or `.pyi` files for type information. If a package + contains only compiled extensions (`.so` or `.pyd` files), you'll need stub files (`.pyi`) for ty + to understand the types. See also [this issue](https://github.com/astral-sh/ty/issues/487) which + tracks improvements in this area. + +## Does ty support monorepos? + +ty can work with monorepos, but automatic discovery of nested projects is limited. By default, ty +uses the current working directory or the `--project` option to determine the project root. + +For monorepos with multiple Python packages, you have a few options: + +1. **Run ty per-package**: Run `ty check` from each package directory, or use `--project` to specify + the package: + + ```bash + ty check --project packages/package-a + ty check --project packages/package-b + ``` + +1. **Configure multiple source roots**: Use [`environment.root`](./configuration.md#root) to specify + multiple source directories: + + ```toml + [tool.ty.environment] + root = ["packages/package-a", "packages/package-b"] + ``` + + This has the disadvantage of treating all packages as a single project, which may lead to cases + in which ty thinks something is importable when it wouldn't be at runtime. + +You can follow [this issue](https://github.com/astral-sh/ty/issues/819) to get updates on this +topic. + +## Does ty support PEP 723 inline-metadata scripts? + +It depends on what you want to do. If you have a single inline-metadata script, you can type check +it with ty by using uv's `--with-requirements` flag to install the dependencies specified in the +script header: + +```bash +uvx --with-requirements script.py ty check script.py +``` + +If you have multiple scripts in your workspace, ty does not yet recognize that they have different +dependencies based on their inline metadata. + +You can follow [this issue](https://github.com/astral-sh/ty/issues/691) for updates. + +## Is there a pre-commit hook for ty? + +Not yet. You can track progress in [this issue](https://github.com/astral-sh/ty/issues/269), which +also includes some suggested manual hooks you can use in the meantime. + +## Does ty support (mypy) plugins? + +No. ty does not have a plugin system and there is currently no plan to add one. + +We prefer extending the type system with well-specified features rather than relying on +type-checker-specific plugins. That said, we are considering adding support for popular third-party +libraries like pydantic, SQLAlchemy, attrs, or django directly into ty. + +## What is `Top[list[Unknown]]`, and why does it appear? + +This type represents "all possible lists of any element type" (as opposed to `list[Unknown]`, which +represents "a list of some unknown element type"). It usually arises from a check such as +`if isinstance(x, list):`. If `x` was previously of type `Item | list[Item]`, you might expect this +check to narrow the type to `list[Item]`, but ty respects the possibility that there could be a +common subclass of both `Item` and `list` (which may not be a list of `Item`!), and so the narrowed +type is instead `(Item & Top[list[Unknown]]) | list[Item]`. This code can be made more robust by +instead checking `if instance(x, Item)`, or by declaring the `Item` type as `@typing.final`. + +See also the [discussion +here](https://docs.astral.sh/ty/features/type-system/#top-and-bottom-materializations) and [in this +issue](https://github.com/astral-sh/ty/issues/1578). diff --git a/mkdocs.yml b/mkdocs.yml index 070f44a..c9de9b8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -99,6 +99,7 @@ nav: - Language server: features/language-server.md - Reference: - Configuration: reference/configuration.md + - Typing FAQ: reference/typing-faq.md - Rules: reference/rules.md - CLI: reference/cli.md - Exit codes: reference/exit-codes.md diff --git a/pyproject.toml b/pyproject.toml index 553e515..57b7ed7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ty" -version = "0.0.2" +version = "0.0.5" requires-python = ">=3.8" dependencies = [] description = "An extremely fast Python type checker, written in Rust." @@ -102,6 +102,7 @@ include = [ version-files = [ "pyproject.toml", { path = "dist-workspace.toml", field = "workspace.version", format = "cargo" }, + "docs/installation.md", ] submodules = ["ruff"] require-labels = [{ submodule = "ruff", labels = ["ty"] }] @@ -117,7 +118,6 @@ changelog-ignore-authors = ["github-actions"] major-labels = [] # We do not use the major version number yet minor-labels = [] # We do not use the minor version number yet version-format = "cargo" -default-bump-type = "pre" trim-title-prefixes = ["[ty]"] [tool.rooster.section-labels] diff --git a/ruff b/ruff index ad3de4e..ad41728 160000 --- a/ruff +++ b/ruff @@ -1 +1 @@ -Subproject commit ad3de4e4881a15545fc85fdfaf5374df7793538d +Subproject commit ad41728204681a60e6d9761857b000cb6bfe732b diff --git a/uv.lock b/uv.lock index ce8a6d9..0de1e2b 100644 --- a/uv.lock +++ b/uv.lock @@ -624,7 +624,7 @@ wheels = [ [[package]] name = "ty" -version = "0.0.2" +version = "0.0.5" source = { editable = "." } [package.dev-dependencies]