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-zeroAlways use exit code 0, even when there are error-level diagnostics
--extra-search-path pathAdditional 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-excludeEnforce exclusions, even for paths passed to ty directly on the command-line. Use --no-force-exclude to disable
--help, -hPrint help (see a summary with '-h')
--ignore ruleDisables the rule. Can be specified multiple times.
--no-progressHide 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 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]