| { |
| "$schema": "http://json-schema.org/draft-07/schema#", |
| "title": "Options", |
| "type": "object", |
| "properties": { |
| "analysis": { |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/AnalysisOptions" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| }, |
| "environment": { |
| "description": "Configures the type checking environment.", |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/EnvironmentOptions" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| }, |
| "overrides": { |
| "description": "Override configurations for specific file patterns.\n\nEach override specifies include/exclude patterns and rule configurations\nthat apply to matching files. Multiple overrides can match the same file,\nwith later overrides taking precedence.", |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/OverridesOptions" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| }, |
| "rules": { |
| "description": "Configures the enabled rules and their severity.\n\nThe keys are either rule names or `all` to set a default severity for all rules.\nSee [the rules documentation](https://ty.dev/rules) for a list of all available rules.\n\nValid severities are:\n\n* `ignore`: Disable the rule.\n* `warn`: Enable the rule and create a warning diagnostic.\n* `error`: Enable the rule and create an error diagnostic.\n\nBy default, ty exits with code 1 if it emits any warning or error diagnostics.\nSet `terminal.error-on-warning` to `false` to exit with code 0 if all diagnostics have `warning` severity.", |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/Rules" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| }, |
| "src": { |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/SrcOptions" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| }, |
| "terminal": { |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/TerminalOptions" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| } |
| }, |
| "additionalProperties": false, |
| "definitions": { |
| "AnalysisOptions": { |
| "type": "object", |
| "properties": { |
| "allowed-unresolved-imports": { |
| "description": "A list of module glob patterns for which `unresolved-import` diagnostics should be suppressed.\n\nDetails on supported glob patterns:\n- `*` matches zero or more characters except `.`. For example, `foo.*` matches `foo.bar` but\n not `foo.bar.baz`; `foo*` matches `foo` and `foobar` but not `foo.bar` or `barfoo`; and `*foo`\n matches `foo` and `barfoo` but not `foo.bar` or `foobar`.\n- `**` matches any number of module components (e.g., `foo.**` matches `foo`, `foo.bar`, etc.)\n- Prefix a pattern with `!` to exclude matching modules\n\nWhen multiple patterns match, later entries take precedence.\n\nGlob patterns can be used in combinations with each other. For example, to suppress errors for\nany module where the first component contains the substring `test`, use `*test*.**`.", |
| "type": [ |
| "array", |
| "null" |
| ], |
| "items": { |
| "$ref": "#/definitions/string" |
| } |
| }, |
| "replace-imports-with-any": { |
| "description": "A list of module glob patterns whose imports should be replaced with `typing.Any`.\n\nUnlike `allowed-unresolved-imports`, this setting replaces the module's type information\nwith `typing.Any` even if the module can be resolved. Import diagnostics are\nunconditionally suppressed for matching modules.\n\n- Prefix a pattern with `!` to exclude matching modules\n\nWhen multiple patterns match, later entries take precedence.\n\nGlob patterns can be used in combinations with each other. For example, to suppress errors for\nany module where the first component contains the substring `test`, use `*test*.**`.\n\nWhen multiple patterns match, later entries take precedence.", |
| "type": [ |
| "array", |
| "null" |
| ], |
| "items": { |
| "$ref": "#/definitions/string" |
| } |
| }, |
| "respect-type-ignore-comments": { |
| "description": "Whether ty should respect `type: ignore` comments.\n\nWhen set to `false`, `type: ignore` comments are treated like any other normal\ncomment and can't be used to suppress ty errors (you have to use `ty: ignore` instead).\n\nSetting this option can be useful when using ty alongside other type checkers or when\nyou prefer using `ty: ignore` over `type: ignore`.\n\nDefaults to `true`.", |
| "type": [ |
| "boolean", |
| "null" |
| ] |
| }, |
| "strict-equality-semantics": { |
| "description": "Configure ty's behavior regarding type inference and narrowing of equality\nchecks. Defaults to `false`.\n\nBy default, ty makes various assumptions about equality checks that match the\nintuitions of most Python programmers, but may not be fully sound in all situations.\nEnabling this option makes ty more conservative about these assumptions, making it\nless likely to infer `Literal[True]` or `Literal[False]` as the result of an\nequality check. This has various effects on type checking, including fewer type\nnarrowing opportunities and more conservative assumptions regarding control flow.\n\nOne way in which ty will by default make unsound assumptions is by narrowing an\nobject `x` of type `str` to `Literal[\"a\"]` after an `if x == \"a\"` check. This is\nunsound because a subclass of `str` with value `\"a\"` will (by default) compare equal\nto `\"a\"`, but will not be of type `Literal[\"a\"]`:\n\n```pycon\n>>> # `Literal[\"a\"]` can only be inhabited by instances of exactly `str`, not\n>>> # subclasses, but str subclasses compare equal by default:\n>>> class StringSubclass(str): ...\n...\n>>> StringSubclass(\"a\") == \"a\"\nTrue\n>>>\n>>> # This also applies to `StrEnum`s:\n>>> from enum import StrEnum\n>>> class MyEnum(StrEnum):\n... A = \"a\"\n...\n>>> MyEnum.A == \"a\"\nTrue\n```\n\nEnabling this option prevents the unsound narrowing of `x` to `Literal[\"a\"]`,\nand instead keeps it as `str`:\n\n```python\nfrom typing import Literal\n\ndef parse(value: str) -> Literal[\"a\"] | None:\n # with `strict-equality-semantics = true`, no narrowing will occur here,\n # and an error will be emitted on the `return` statement.\n if value == \"a\":\n return value\n return None\n```\n\nAnother assumption ty makes by default is that subclasses will never override `__eq__` or\n`__ne__`. This allows ty to narrow the following union based on an equality check, despite\nthe fact that an instance of a subclass of `Foo` could compare equal to `None`, and it's\nperfectly valid to pass an instance of a subclass into the `x` parameter of this function:\n\n```python\ndef narrow(x: Foo | None, other: Foo) -> None:\n if x == other:\n # with this option enabled, `x` will still have type `Foo | None` here,\n # since it is legal to subclass `Foo` and override its `__eq__` method.\n reveal_type(x)\n```\n\nMany operations in Python implicitly call `__eq__` under the hood; enabling this option\nwill also impact those operations. For example, this option will also impact narrowing from\n`in` checks, and narrowing in `match` statements that use value patterns:\n\n```python\ndef narrow_in(x: Foo | None, other: list[Foo]) -> None:\n if x in other:\n # with this option enabled, `x` will still have type `Foo | None` here,\n # since the `in` operator implicitly calls `__eq__` on each element of `other`.\n reveal_type(x)\n\n\ndef narrow_match(x: str) -> None:\n match x:\n case \"a\":\n # with this option enabled, `x` will still have type `str` here,\n # since this `case` branch will be taken by any object that compares\n # equal to `\"a\"`, including subclasses of `str`.\n reveal_type(x)\n```", |
| "type": [ |
| "boolean", |
| "null" |
| ] |
| }, |
| "strict-generic-narrowing": { |
| "description": "Whether ty should use strict narrowing for unspecialized generic classes in\n`isinstance()` and `issubclass()` checks, `match` class patterns, and `TypeIs` checks.\n\nWhen enabled, ty narrows to the top materialization of the class. For example,\n`isinstance(value, list)` narrows a value of type `object` to `Top[list[Unknown]]`,\nrepresenting the (infinite) union of all possible `list` specializations. Iterating\nover the list would yield values of type `object`.\n\nWhen disabled, ty uses gradual generic narrowing, preserving compatible type\narguments from the original type where possible. For example,\n`isinstance(value, list)` narrows a value of type `Sequence[int]` to `list[int]`.\nIf no specialization is available, the same check narrows a value of type `object`\nto `list[Unknown]`; items of any type can then be appended to the list. Class\npatterns such as `case list():` follow the same behavior.\n\nDefaults to `false`.", |
| "type": [ |
| "boolean", |
| "null" |
| ] |
| } |
| }, |
| "additionalProperties": false |
| }, |
| "Array_of_string": { |
| "type": "array", |
| "items": { |
| "$ref": "#/definitions/string" |
| } |
| }, |
| "EnvironmentOptions": { |
| "type": "object", |
| "properties": { |
| "extra-paths": { |
| "description": "User-provided paths that should take first priority in module resolution.\n\nThis is an advanced option that should usually only be used for first-party or third-party\nmodules that are not installed into your Python environment in a conventional way.\nUse the `python` option to specify the location of your Python environment.\n\nThis option is similar to mypy's `MYPYPATH` environment variable and pyright's `stubPath`\nconfiguration setting.", |
| "type": [ |
| "array", |
| "null" |
| ], |
| "items": { |
| "$ref": "#/definitions/RelativePathBuf" |
| } |
| }, |
| "python": { |
| "description": "Path to your project's Python environment or interpreter.\n\nty uses the `site-packages` directory of your project's Python environment\nto resolve third-party (and, in some cases, first-party) imports in your code.\n\nThis can be a path to:\n\n- A Python interpreter, e.g. `.venv/bin/python3`\n- A virtual environment directory, e.g. `.venv`\n- A system Python [`sys.prefix`] directory, e.g. `/usr`\n\nIf you're using a project management tool such as uv, you should not generally need to\nspecify this option, as commands such as `uv run` will set the `VIRTUAL_ENV` environment\nvariable to point to your project's virtual environment. ty can also infer the location of\nyour environment from an activated Conda environment, and will look for a `.venv` directory\nin the project root if none of the above apply. Failing that, ty will look for a `python3`\nor `python` binary available in `PATH`.\n\nScripts with inline metadata use their own Python environment. They can use an explicitly\nconfigured environment, an activated environment, or an environment selected by the editor.\nUnlike projects, they do not automatically use a `.venv` directory.\n\n[`sys.prefix`]: https://docs.python.org/3/library/sys.html#sys.prefix", |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/RelativePathBuf" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| }, |
| "python-platform": { |
| "description": "Specifies the target platform that will be used to analyze the source code.\nIf specified, ty will understand conditions based on comparisons with `sys.platform`, such\nas are commonly found in typeshed to reflect the differing contents of the standard library across platforms.\nIf `all` is specified, ty will assume that the source code can run on any platform.\n\nIf no platform is specified, ty will use the current platform:\n- `win32` for Windows\n- `darwin` for macOS\n- `android` for Android\n- `ios` for iOS\n- `linux` for everything else", |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/PythonPlatform" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| }, |
| "python-version": { |
| "description": "Specifies the version of Python that will be used to analyze the source code.\nThe version should be specified as a string in the format `M.m` where `M` is the major version\nand `m` is the minor (e.g. `\"3.7\"` or `\"3.12\"`).\nIf a version is provided, ty will generate errors if the source code makes use of language features\nthat are not supported in that version.\n\nty officially supports type checking code that targets Python 3.10 and later. Python 3.7\nthrough 3.9 can still be selected, but ty may produce false positives or false negatives for\nstandard-library APIs because its bundled stubs do not fully describe those versions.\n\nIf a version is not specified, ty will try the following techniques in order of preference\nto determine a value:\n1. Check for the `project.requires-python` setting in a `pyproject.toml` file\n and use the minimum version from the specified range\n2. Check for an activated or configured Python environment\n and attempt to infer the Python version of that environment\n3. Fall back to the default value (see below)\n\nScripts with inline metadata use their `requires-python` field instead of\n`project.requires-python`. They do not inherit the Python version of the enclosing project.\n\nFor some language features, ty can also understand conditionals based on comparisons\nwith `sys.version_info`. These are commonly found in typeshed, for example,\nto reflect the differing contents of the standard library across Python versions.", |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/SupportedPythonVersion" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| }, |
| "root": { |
| "description": "The root paths of the project, used for finding first-party modules.\n\nAccepts a list of directory paths searched in priority order (first has highest priority).\n\nIf left unspecified, ty will try to detect common project layouts and initialize `root` accordingly.\nThe project root (`.`) is always included. Additionally, the following directories are included\nif they exist and are not packages (i.e. they do not contain `__init__.py` or `__init__.pyi` files):\n\n* `./src`\n* `./<project-name>` (if a `./<project-name>/<project-name>` directory exists)\n* `./python`\n\nScripts with inline metadata have no first-party roots by default because they are\nsingle-file programs. Set `root = [\".\"]` to allow importing local modules.", |
| "type": [ |
| "array", |
| "null" |
| ], |
| "items": { |
| "$ref": "#/definitions/RelativePathBuf" |
| } |
| }, |
| "typeshed": { |
| "description": "Optional path to a \"typeshed\" directory on disk for us to use for standard-library types.\nIf this is not provided, we will fallback to our vendored typeshed stubs for the stdlib,\nbundled as a zip file in the binary", |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/RelativePathBuf" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| } |
| }, |
| "additionalProperties": false |
| }, |
| "Level": { |
| "oneOf": [ |
| { |
| "title": "Ignore", |
| "description": "The lint is disabled and should not run.", |
| "type": "string", |
| "const": "ignore" |
| }, |
| { |
| "title": "Warn", |
| "description": "The lint is enabled and diagnostic should have a warning severity.", |
| "type": "string", |
| "const": "warn" |
| }, |
| { |
| "title": "Error", |
| "description": "The lint is enabled and diagnostics have an error severity.", |
| "type": "string", |
| "const": "error" |
| } |
| ] |
| }, |
| "OutputFormat": { |
| "description": "The diagnostic output format.", |
| "oneOf": [ |
| { |
| "description": "The default full mode will print \"pretty\" diagnostics.\n\nThat is, color will be used when printing to a `tty`.\nMoreover, diagnostic messages may include additional\ncontext and annotations on the input to help understand\nthe message.", |
| "type": "string", |
| "const": "full" |
| }, |
| { |
| "description": "Print diagnostics in a concise mode.\n\nThis will guarantee that each diagnostic is printed on\na single line. Only the most important or primary aspects\nof the diagnostic are included. Contextual information is\ndropped.\n\nThis may use color when printing to a `tty`.", |
| "type": "string", |
| "const": "concise" |
| }, |
| { |
| "description": "Print diagnostics in the JSON format expected by GitLab [Code Quality] reports.\n\n[Code Quality]: https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format", |
| "type": "string", |
| "const": "gitlab" |
| }, |
| { |
| "description": "Print diagnostics in the format used by [GitHub Actions] workflow error annotations.\n\n[GitHub Actions]: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#setting-an-error-message", |
| "type": "string", |
| "const": "github" |
| }, |
| { |
| "description": "Print diagnostics as a JUnit-style XML report.", |
| "type": "string", |
| "const": "junit" |
| } |
| ] |
| }, |
| "OverrideOptions": { |
| "type": "object", |
| "properties": { |
| "analysis": { |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/AnalysisOptions" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| }, |
| "exclude": { |
| "description": "A list of file and directory patterns to exclude from this override.\n\nPatterns follow a syntax similar to `.gitignore`.\nExclude patterns take precedence over include patterns within the same override.\n\nIf not specified, defaults to `[]` (excludes no files).", |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/Array_of_string" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| }, |
| "include": { |
| "description": "A list of file and directory patterns to include for this override.\n\nThe `include` option follows a similar syntax to `.gitignore` but reversed:\nIncluding a file or directory will make it so that it (and its contents)\nare affected by this override.\n\nIf not specified, defaults to `[\"**\"]` (matches all files).", |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/Array_of_string" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| }, |
| "rules": { |
| "description": "Rule overrides for files matching the include/exclude patterns.\n\nThese rules will be merged with the global rules, with override rules\ntaking precedence for matching files. You can set rules to different\nseverity levels or disable them entirely.", |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/Rules" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| } |
| }, |
| "additionalProperties": false |
| }, |
| "OverridesOptions": { |
| "description": "Configuration override that applies to specific files based on glob patterns.\n\nAn override allows you to apply different rule configurations to specific\nfiles or directories. Multiple overrides can match the same file, with\nlater overrides take precedence. Override rules take precedence over global\nrules for matching files.\n\nFor example, to relax enforcement of rules in test files:\n\n```toml\n[[tool.ty.overrides]]\ninclude = [\"tests/**\", \"**/test_*.py\"]\n\n[tool.ty.overrides.rules]\npossibly-unresolved-reference = \"warn\"\n```\n\nOr, to ignore a rule in generated files but retain enforcement in an important file:\n\n```toml\n[[tool.ty.overrides]]\ninclude = [\"generated/**\"]\nexclude = [\"generated/important.py\"]\n\n[tool.ty.overrides.rules]\npossibly-unresolved-reference = \"ignore\"\n```", |
| "type": "array", |
| "items": { |
| "$ref": "#/definitions/OverrideOptions" |
| } |
| }, |
| "PythonPlatform": { |
| "description": "The target platform to assume when resolving types.\n", |
| "anyOf": [ |
| { |
| "type": "string" |
| }, |
| { |
| "description": "Do not make any assumptions about the target platform.", |
| "const": "all" |
| }, |
| { |
| "description": "Darwin", |
| "const": "darwin" |
| }, |
| { |
| "description": "Linux", |
| "const": "linux" |
| }, |
| { |
| "description": "Windows", |
| "const": "win32" |
| } |
| ] |
| }, |
| "RelativePathBuf": { |
| "description": "A possibly relative path in a configuration file.\n\nRelative paths in configuration files or from CLI options\nrequire different anchoring:\n\n* CLI: The path is relative to the current working directory\n* Configuration file: The path is relative to the project's or script's configuration root.", |
| "allOf": [ |
| { |
| "$ref": "#/definitions/SystemPathBuf" |
| } |
| ] |
| }, |
| "Rules": { |
| "type": "object", |
| "properties": { |
| "abstract-and-final-method": { |
| "title": "detects methods that are both abstract and final", |
| "description": "## What it does\n\nChecks for methods decorated with both `@abstractmethod` and `@final`.\n\n## Why is this bad?\n\nAn abstract method must be overridden for a subclass to become concrete, but a final method cannot\nbe overridden. Combining the decorators therefore makes it impossible for a subclass to provide a\nconcrete implementation.\n\n## Example\n\n```python\nfrom abc import ABC, abstractmethod\nfrom typing import final\n\n\nclass Base(ABC):\n @final\n @abstractmethod\n def method(self) -> None: ... # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "abstract-method-in-final-class": { |
| "title": "detects `@final` classes with unimplemented abstract methods", |
| "description": "## What it does\n\nChecks for `@final` classes that have unimplemented abstract methods.\n\n## Why is this bad?\n\nA class decorated with `@final` cannot be subclassed. If such a class has abstract methods that are\nnot implemented, the class can never be properly instantiated, as the abstract methods can never be\nimplemented (since subclassing is prohibited).\n\nAt runtime, instantiation of classes with unimplemented abstract methods is only prevented for\nclasses that have `ABCMeta` (or a subclass of it) as their metaclass. However, type checkers also\nenforce this for classes that do not use `ABCMeta`, since the intent for the class to be abstract is\nclear from the use of `@abstractmethod`.\n\n## Example\n\n```python\nfrom abc import ABC, abstractmethod\nfrom typing import final\n\n\nclass Base(ABC):\n @abstractmethod\n def method(self) -> int: ...\n\n\n@final\n# `Derived` does not implement `method`\nclass Derived(Base): # error\n pass\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "all": { |
| "title": "set the default severity level for all rules", |
| "description": "Configure a default severity level for all rules. Individual rule settings override this default.", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "ambiguous-protocol-member": { |
| "title": "detects protocol classes with ambiguous interfaces", |
| "description": "## What it does\n\nChecks for protocol classes with members that will lead to ambiguous interfaces.\n\n## Why is this bad?\n\nAssigning to an undeclared variable in a protocol class, or to an undeclared attribute through a\nprotocol method's `self` or `cls` receiver, leads to an ambiguous interface which may lead to the\ntype checker inferring unexpected things. It's recommended to ensure that all members of a protocol\nclass are explicitly declared.\n\n## Examples\n\n```py\nfrom typing import ClassVar, Protocol\n\n\nclass BaseProto(Protocol):\n a: int # fine (explicitly declared as `int`)\n instance_member: str\n class_member: ClassVar[str]\n\n # fine: a method definition using `def` is considered a declaration\n def method_member(self) -> int: ...\n\n def method(self) -> None:\n self.instance_member = \"value\" # fine (declared in the class body)\n self.implicit = \"value\" # error: [ambiguous-protocol-member]\n\n @classmethod\n def class_method(cls) -> None:\n cls.class_member = \"value\" # fine (declared in the class body)\n cls.implicit_class = \"value\" # error: [ambiguous-protocol-member]\n\n # no explicit declaration, leading to ambiguity\n c = \"some variable\" # error\n # no explicit declaration, leading to ambiguity\n b = method_member # error\n\n # This creates implicit assignments of `d` and `e` in the protocol class body.\n # Were they really meant to be considered protocol members?\n # error: \"`d` is not declared as a protocol member\"\n # error: \"`e` is not declared as a protocol member\"\n for d, e in enumerate(range(42)):\n pass\n\n\nclass SubProto(BaseProto, Protocol):\n a = 42 # fine (declared in superclass)\n```", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "assert-type-unspellable-subtype": { |
| "title": "detects failed type assertions", |
| "description": "## What it does\n\nChecks for `assert_type()` calls where the actual type is an unspellable subtype of the asserted\ntype.\n\n## Why is this bad?\n\n`assert_type()` is intended to ensure that the inferred type of a value is exactly the same as the\nasserted type. But in some situations, ty has nonstandard extensions to the type system that allow\nit to infer more precise types than can be expressed in user annotations. ty emits a different error\ncode to `type-assertion-failure` in these situations so that users can easily differentiate between\nthe two cases.\n\n## Example\n\n```toml\n[environment]\npython-version = \"3.11\"\n```\n\n```python\nfrom typing import assert_type\n\n\ndef _(x: int):\n assert_type(x, int) # fine\n if x:\n # the actual type is `int & ~AlwaysFalsy`,\n # which excludes types like `Literal[0]`\n # error: [assert-type-unspellable-subtype]\n assert_type(x, int)\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "blanket-ignore-comment": { |
| "title": "detects blanket `ty: ignore` comments", |
| "description": "## What it does\n\nChecks for `ty: ignore` comments that don't specify which rules to ignore.\n\n## Why is this bad?\n\nA blanket `ty: ignore` comment suppresses every type-checking diagnostic on the applicable line or\nfile. Specifying rule codes documents which diagnostics are expected and prevents the comment from\nsilencing unrelated errors.\n\n## Examples\n\n```py\n# error\nvalue = unknown # ty: ignore\n```\n\nUse instead:\n\n```py\nvalue = unknown # ty: ignore[unresolved-reference]\n```", |
| "default": "ignore", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "call-abstract-method": { |
| "title": "detects calls to abstract methods with trivial bodies on class objects", |
| "description": "## What it does\n\nChecks for calls to abstract `@classmethod`s or `@staticmethod`s with \"trivial bodies\" when accessed\non the class object itself.\n\n\"Trivial bodies\" are bodies that solely consist of `...`, `pass`, a docstring, and/or\n`raise NotImplementedError`.\n\n## Why is this bad?\n\nAn abstract method with a trivial body has no concrete implementation to execute, so calling such a\nmethod directly on the class will probably not have the desired effect.\n\nIt is also unsound to call these methods directly on the class. Unlike other methods, ty permits\nabstract methods with trivial bodies to have non-`None` return types even though they always return\n`None` at runtime. This is because it is expected that these methods will always be overridden\nrather than being called directly. As a result of this exception to the normal rule, ty may infer an\nincorrect type if one of these methods is called directly, which may then mean that type errors\nelsewhere in your code go undetected by ty.\n\nCalling abstract classmethods or staticmethods via `type[X]` is allowed, since the actual runtime\ntype could be a concrete subclass with an implementation.\n\n## Example\n\n```python\nfrom abc import ABC, abstractmethod\n\n\nclass Foo(ABC):\n @classmethod\n @abstractmethod\n def method(cls) -> int: ...\n\n\n# cannot call abstract classmethod\nFoo.method() # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "call-non-callable": { |
| "title": "detects calls to non-callable objects", |
| "description": "## What it does\n\nChecks for calls to non-callable objects.\n\n## Why is this bad?\n\nCalling a non-callable object will raise a `TypeError` at runtime.\n\n## Examples\n\n```python\n# TypeError: 'int' object is not callable\n4() # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "call-top-callable": { |
| "title": "detects calls to the top callable type", |
| "description": "## What it does\n\nChecks for calls to objects typed as `Top[Callable[..., T]]` (the infinite union of all callable\ntypes with return type `T`).\n\n## Why is this bad?\n\nWhen `analysis.strict-generic-narrowing` is enabled, `callable(x)` and `isinstance(x, Callable)`\nnarrow an object to `Top[Callable[..., object]]`. We know the object is callable, but we don't know\nits precise signature. This type represents the set of all possible callable types (including, e.g.,\nfunctions that take no arguments and functions that require arguments), so no specific set of\narguments can be guaranteed to be valid.\n\n## Examples\n\n```toml\n[analysis]\nstrict-generic-narrowing = true\n```\n\n```python\ndef f(x: object):\n if callable(x):\n # We know `x` is callable, but not what arguments it accepts\n x() # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "conflicting-declarations": { |
| "title": "detects conflicting declarations", |
| "description": "## What it does\n\nChecks whether a variable has been declared as two conflicting types.\n\n## Why is this bad\n\nA variable with two conflicting declarations likely indicates a mistake. Moreover, it could lead to\nincorrect or ill-defined type inference for other code that relies on these variables.\n\n## Examples\n\n```python\nif __name__ == \"__main__\":\n a: int\nelse:\n a: str\n\na = 1 # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "conflicting-metaclass": { |
| "title": "detects conflicting metaclasses", |
| "description": "## What it does\n\nChecks for class definitions where the metaclass of the class being created would not be a subclass\nof the metaclasses of all the class's bases.\n\n## Why is it bad?\n\nSuch a class definition raises a `TypeError` at runtime.\n\n## Examples\n\n```pyi\nclass M1(type): ...\nclass M2(type): ...\nclass A(metaclass=M1): ...\nclass B(metaclass=M2): ...\n\n# TypeError: metaclass conflict\nclass C(A, B): ... # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "cyclic-class-definition": { |
| "title": "detects cyclic class definitions", |
| "description": "## What it does\n\nChecks for class definitions in stub files that inherit (directly or indirectly) from themselves.\n\n## Why is it bad?\n\nAlthough forward references are natively supported in stub files, inheritance cycles are still\ndisallowed, as it is impossible to resolve a consistent [method resolution order] for a class that\ninherits from itself.\n\n## Examples\n\n`foo.pyi`:\n\n```pyi\nclass A(B): ... # error\nclass B(A): ... # error\n```\n\n[method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "cyclic-type-alias-definition": { |
| "title": "detects cyclic type alias definitions", |
| "description": "## What it does\n\nChecks for circular type alias definitions.\n\n## Why is it bad?\n\nRecursive aliases are valid when recursive references occur inside another type, such as\n`list[Tree]`. An alias cannot expand directly to itself or include itself as a union member. This\napplies to both `type` statements and aliases created with `TypeAliasType`.\n\n## Examples\n\n```toml\n[environment]\npython-version = \"3.12\"\n```\n\n```python\nfrom typing import TypeAliasType\n\ntype Itself = Itself # error\n\ntype A = B # error\ntype B = A # error\n\ntype IntOr = int | IntOr # error\n\nCycle = TypeAliasType(\"Cycle\", \"Cycle\") # error\n\ntype Tree = int | list[Tree] # valid recursive alias\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "dataclass-field-order": { |
| "title": "detects dataclass definitions with required fields after fields with default values", |
| "description": "## What it does\n\nChecks for dataclass definitions where required fields are defined after fields with default values.\n\n## Why is this bad?\n\nIn dataclasses, all required fields (fields without default values) must be defined before fields\nwith default values. This is a Python requirement that will raise a `TypeError` at runtime if\nviolated.\n\n## Example\n\n```python\nfrom dataclasses import dataclass\n\n\n@dataclass\nclass Example:\n x: int = 1 # Field with default value\n # Required field after field with default\n y: str # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "deprecated": { |
| "title": "detects uses of deprecated items", |
| "description": "## What it does\n\nChecks for uses of deprecated items\n\n## Why is this bad?\n\nDeprecated items should no longer be used.\n\n## Examples\n\n```toml\n[environment]\npython-version = \"3.13\"\n```\n\n```python\nimport warnings\n\n\n@warnings.deprecated(\"use new_func instead\")\ndef old_func(): ...\n\n\nold_func() # error: [deprecated]\n```", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "disjoint-cast": { |
| "title": "detects `cast` calls between disjoint types", |
| "description": "## What it does\n\nDetects `cast` calls where the inferred type of the value is disjoint from the destination type.\n\nTwo types are disjoint if they are entirely non-overlapping. For example, `str` and `int` are\ndisjoint types because it is impossible to create a Python object that is both a `str` and an `int`\nat the same time: Python forbids multiple inheritance between these two classes:\n\n```pycon\n>>> class StrAndInt(int, str): ...\nTraceback (most recent call last):\n File \"<python-input-0>\", line 1, in <module>\n class StrAndInt(int, str): ...\nTypeError: multiple bases have instance lay-out conflict\n```\n\nThis means that any object of type `int` can never also be of type `str`, and any object of type\n`str` can never also inhabit the type `int`. The only common subtype of these two types is\n[`Never`][never], the uninhabited type, which has no members.\n\n## Why is this bad?\n\n`cast()` is deliberately designed as an \"escape hatch\" in the type system that is neither validated\nat runtime nor, by default, by type checkers. While upcasting to a supertype is always sound, and\ncasting to a subtype can be sound in some situations if accompanied by careful validation checks,\n`cast()` is also deliberately designed to allow unsound narrowing, and most useful applications of\n`cast()` in real-world code cannot be fully validated by a type checker.\n\nNonetheless, even while acknowledging the fact that `cast()` is intentionally designed to allow\nunsoundness, casting a value to an entirely *disjoint* type is especially likely to indicate a\nmistake in your code. A cast from an `int` to a `str`, for example, likely indicates a bug or\nmisunderstanding.\n\nThis rule therefore provides a means for codebases to partially validate their uses of `cast()`\nwithout banning the API -- or even banning all unsound uses of the API -- entirely.\n\n## Example\n\n```py\nfrom typing import cast\n\n\ndef parse(value: int) -> str:\n return cast(str, value) # error: [disjoint-cast]\n```\n\nCasts between overlapping (non-disjoint) types are allowed:\n\n```py\nfrom collections.abc import Sequence\nfrom typing import cast\n\n\ndef validate(numbers: Sequence[int | None]) -> Sequence[int]:\n if None in numbers:\n raise TypeError(\"must provide a sequence of numbers!\")\n return cast(Sequence[int], numbers)\n```\n\nNote that disjointness between types can sometimes be surprising. For example, `list[int]` is\ndisjoint from `list[bool]` even though `bool` is a subtype of `int`. Due to the fact that `list` is\n[mutable and invariant], it would be deeply unsound for ty to ever narrow an object of type\n`list[int]` to the type `list[bool]`. As such, ty will complain about a cast from `list[int]` to\n`list[bool]` when this rule is enabled.\n\nSimilarly, two `NewType`s can be disjoint even when they share the same underlying nominal base\ntype, unless one `NewType` is explicitly declared as a sub-newtype of the other.\n\n```py\nfrom typing import NewType, cast\n\n\nUserId = NewType(\"UserId\", int)\nProUserId = NewType(\"ProUserId\", int)\n\n\ndef f(x: list[int], user_id: UserId):\n y = cast(list[bool], x) # error: [disjoint-cast]\n pro_user_id = cast(ProUserId, user_id) # error: [disjoint-cast]\n```\n\n## Alternatives\n\nIn many cases, the diagnostic can be avoided by switching to use covariant generic types rather than\ninvariant ones:\n\n```py\n# `Sequence`, unlike `list`, is immutable and covariant\nfrom collections.abc import Sequence\nfrom typing import cast\n\n\ndef f(x: Sequence[int]):\n y = cast(Sequence[bool], x) # no diagnostic\n```\n\nThough if you're able to use covariant types, a type-safe narrowing mechanism that provides runtime\nvalidation, such as using `TypeIs`, is generally preferable to using `cast`:\n\n```py\n# `Sequence`, unlike `list`, is immutable and covariant\nfrom collections.abc import Sequence\nfrom typing_extensions import TypeIs, reveal_type\n\n\ndef is_sequence_of_bools(x: Sequence[int]) -> TypeIs[Sequence[bool]]:\n return all(isinstance(item, bool) for item in x)\n\n\ndef f(x: Sequence[int]):\n assert is_sequence_of_bools(x)\n reveal_type(x) # revealed: Sequence[bool]\n```\n\nIf you're unable to switch to an immutable, covariant generic type, other solutions to this\nparticular diagnostic might include assigning a new list altogether:\n\n```py\ndef f(x: list[int]):\n y: list[bool] = []\n for item in x:\n assert isinstance(item, bool)\n y.append(item)\n```\n\nOr using a `TypeGuard`. While the \"narrowing\" below is still unsound, there is at least some runtime\nvalidation of the element types taking place, making it superior to the `cast`:\n\n```py\nfrom typing_extensions import TypeGuard, reveal_type\n\n\ndef is_list_of_bools(x: list[int]) -> TypeGuard[list[bool]]:\n return all(isinstance(item, bool) for item in x)\n\n\ndef f(x: list[int]):\n assert is_list_of_bools(x)\n reveal_type(x) # revealed: list[bool]\n```\n\n## Default level\n\nThis rule is disabled by default. It is designed as a strict rule for users who want additional\nsoundness checks from their type checker, and it may have false positives in some situations.\n\n## See also\n\n- The Ruff rule [`banned-api`][banned-api] can be used to ban the use of `cast()` entirely in your\n codebase.\n- `redundant-cast` detects casts where the value already has the destination type.\n\n[banned-api]: https://docs.astral.sh/ruff/rules/banned-api/\n[mutable and invariant]: https://docs.astral.sh/ty/reference/typing-faq/#invariant-generics\n[never]: https://docs.python.org/3/library/typing.html#typing.Never", |
| "default": "ignore", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "division-by-zero": { |
| "title": "detects division by zero", |
| "description": "## What it does\n\nIt detects division by zero.\n\n## Why is this bad?\n\nDividing by zero raises a `ZeroDivisionError` at runtime.\n\n## Rule status\n\nThis rule is currently disabled by default because of the number of false positives it can produce.\n\n## Examples\n\n```python\n5 / 0 # error\n```", |
| "default": "ignore", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "duplicate-base": { |
| "title": "detects class definitions with duplicate bases", |
| "description": "## What it does\n\nChecks for class definitions with duplicate bases.\n\n## Why is this bad?\n\nClass definitions with duplicate bases raise `TypeError` at runtime.\n\n## Examples\n\n```python\nclass A: ...\n\n\n# TypeError: duplicate base class\nclass B(A, A): ... # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "duplicate-kw-only": { |
| "title": "detects dataclass definitions with more than one usage of `KW_ONLY`", |
| "description": "## What it does\n\nChecks for dataclass definitions with more than one field annotated with `KW_ONLY`.\n\n## Why is this bad?\n\n`dataclasses.KW_ONLY` is a special marker used to emulate the `*` syntax in normal signatures. It\ncan only be used once per dataclass.\n\nAttempting to annotate two different fields with it will lead to a runtime error.\n\n## Examples\n\n```python\nfrom dataclasses import dataclass, KW_ONLY\n\n\n# Crash at runtime\n@dataclass\nclass A: # error\n b: int\n _1: KW_ONLY\n c: str\n _2: KW_ONLY\n d: bytes\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "dynamic-function-decorator-return": { |
| "title": "detects decorators that replace a function with a dynamic type such as `Any`", |
| "description": "## What it does\n\nDetects decorator applications that replace a function with `Any` or another [dynamic type].\n\n## Why is this bad?\n\nA decorator can replace the function it receives with any object. Type checkers therefore use the\ndecorator's return type as the type of the decorated function. If the decorator returns `Any` or\n`Unknown` (explicitly or implicitly), the original type is lost, along with the type checker's\nability to catch invalid calls and attribute accesses:\n\n```py\nfrom collections.abc import Callable\n\n\ndef untyped_decorator(function: Callable[..., object]):\n return function\n\n\n# error: \"Decorator returns `Unknown`\"\n@untyped_decorator\ndef stringify(value: int) -> str:\n return str(value)\n\n\n# No type error is reported, even though `stringify` expects an integer.\nstringify(\"not an integer\")\n```\n\nThis rule identifies the point where a decorator erases useful type information, before that\nimprecision spreads to every use of the decorated function. It can be especially useful in cases\nwhere the decorator is defined in a third-party library. Whereas linter rules such as\n[`ANN201`][ann201] and [`ANN202`][ann202] can complain about missing annotations in your first-party\ncode, they cannot identify instances where unsound types leak into your code due to missing type\nannotations in third-party code installed into `site-packages`.\n\n## Examples\n\n`third_party_library.py`:\n\n```py\nfrom collections.abc import Callable\n\n\ndef untyped_decorator(function: Callable[..., object]):\n return function\n```\n\n`first_party.py`:\n\n```py\nfrom third_party_library import untyped_decorator\n\n\n# error: \"Decorator returns `Unknown`\"\n@untyped_decorator\ndef greet(name: str) -> str:\n return f\"Hello, {name}!\"\n```\n\nIf making a PR to the third-party library to improve their annotations is not possible, fixes for\nthis diagnostic could include writing your own decorator or introducing a type-safe wrapper:\n\n```py\nfrom collections.abc import Callable\nfrom typing import TypeVar\n\nfrom third_party_library import untyped_decorator\n\n\nFunctionT = TypeVar(\"FunctionT\", bound=Callable[..., object])\n\n\ndef typed_wrapper(f: FunctionT) -> FunctionT:\n decorated = untyped_decorator(f)\n assert decorated is f\n return decorated\n\n\n@typed_wrapper\ndef greet(name: str) -> str:\n return f\"Hello, {name}!\"\n```\n\n## Default level\n\nThis rule is disabled by default. It is intended for advanced users wanting additional soundness\nchecks from their type checker, not for users who have just started to use type checkers on their\nPython code.\n\n[ann201]: https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/\n[ann202]: https://docs.astral.sh/ruff/rules/missing-return-type-private-function/\n[dynamic type]: https://typing.python.org/en/latest/spec/glossary.html#term-dynamic-type", |
| "default": "ignore", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "empty-body": { |
| "title": "detects functions with empty bodies that have a non-`None` return type annotation", |
| "description": "## What it does\n\nDetects functions with empty bodies that have a non-`None` return type annotation.\n\nThe errors reported by this rule have the same motivation as the `invalid-return-type` rule. The\ndiagnostic exists as a separate error code to allow users to disable this rule while prototyping\ncode. While we strongly recommend enabling this rule if possible, users migrating from other type\ncheckers may also find it useful to temporarily disable this rule on some or all of their codebase\nif they find it results in a large number of diagnostics.\n\n## Why is this bad?\n\nA function with an empty body (containing only `...`, `pass`, or a docstring) will implicitly return\n`None` at runtime. Returning `None` when the return type is non-`None` is unsound, and will lead to\nty inferring incorrect types elsewhere.\n\nFunctions with empty bodies are permitted in certain contexts where they serve as declarations\nrather than implementations:\n\n- Functions in stub files (`.pyi`)\n- Methods in Protocol classes\n- Abstract methods decorated with `@abstractmethod`\n- Overload declarations decorated with `@overload`\n- Functions in `if TYPE_CHECKING` blocks\n\n## Examples\n\n```python\ndef foo() -> int: ... # error: [empty-body]\n\n\ndef bar() -> str: # error: [empty-body]\n \"\"\"A function that does nothing.\"\"\"\n pass\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "escape-character-in-forward-annotation": { |
| "title": "detects forward type annotations with escape characters", |
| "description": "## What it does\n\nChecks for forward annotations that contain escape characters.\n\n## Why is this bad?\n\nStatic analysis tools like ty can't analyze type annotations that contain escape characters.\n\n## Example\n\n```python\ndef foo() -> \"intt\\b\": ... # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "experimental-syntax": { |
| "title": "detects experimental syntax", |
| "description": "## What it does\n\nChecks for experimental syntax that is not part of the Python typing specification.\n\n## Why is this bad?\n\nExperimental syntax is specific to ty. It may be rejected by other type checkers and may never be\nstandardized, or be subject to breaking changes.\n\n## Examples\n\n```toml\n[environment]\npython-version = \"3.14\"\n```\n\n```python\nclass A: ...\n\n\nclass B: ...\n\n\ndef f(value: A & B) -> None: ... # error: [experimental-syntax]\ndef g(value: ~A) -> None: ... # error: [experimental-syntax]\n```", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "final-on-non-method": { |
| "title": "detects `@final` applied to non-method functions", |
| "description": "## What it does\n\nChecks for `@final` decorators applied to non-method functions.\n\n## Why is this bad?\n\nThe `@final` decorator is only meaningful on methods and classes. Applying it to a module-level\nfunction or a nested function has no effect and is likely a mistake.\n\n## Example\n\n```python\nfrom typing import final\n\n\n# @final is not allowed on non-method functions\n@final # error\ndef my_function() -> int:\n return 0\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "final-without-value": { |
| "title": "detects `Final` declarations without a value", |
| "description": "## What it does\n\nChecks for `Final` symbols that are declared without a value and are never assigned a value in their\nscope.\n\n## Why is this bad?\n\nA `Final` symbol must be initialized with a value at the time of declaration or in a subsequent\nassignment. At module or function scope, the assignment must occur in the same scope. In a class\nbody, the assignment may occur in `__init__`. Protocol members are declarations of an interface and\ndo not require a value.\n\n## Examples\n\n```python\nfrom typing import Final\n\n# `Final` symbol without a value\nMY_CONSTANT: Final[int] # error\n\n# OK: `Final` symbol with a value\nINITIALIZED_CONSTANT: Final[int] = 1\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "ignore-comment-unknown-rule": { |
| "title": "detects `ty: ignore` comments that reference unknown rules", |
| "description": "## What it does\n\nChecks for `ty: ignore[code]` or `type: ignore[ty:code]` comments where `code` isn't a known lint\nrule.\n\n## Why is this bad?\n\nA `ty: ignore[code]` or a `type: ignore[ty:code]` directive with a `code` that doesn't match any\nknown rule will not suppress any type errors, and is probably a mistake.\n\n## Examples\n\n```py\n# error\na = 20 / 1 # ty: ignore[division-by-zer]\n```\n\nUse instead:\n\n```py\na = 20 / 0 # ty: ignore[division-by-zero]\n```", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "implicit-concatenated-string-type-annotation": { |
| "title": "detects implicit concatenated strings in type annotations", |
| "description": "## What it does\n\nChecks for implicit concatenated strings in type annotation positions.\n\n## Why is this bad?\n\nStatic analysis tools like ty can't analyze type annotations that use implicit concatenated strings.\n\n## Examples\n\n<!-- fmt:off -->\n\n```python\nfrom typing import Literal\n\ndef test() -> \"Literal[\" \"5\" \"]\": # error\n return 5\n```\n\n<!-- fmt:on -->\n\nUse instead:\n\n```python\nfrom typing import Literal\n\n\ndef test() -> \"Literal[5]\":\n return 5\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "inconsistent-mro": { |
| "title": "detects class definitions with an inconsistent MRO", |
| "description": "## What it does\n\nChecks for classes with an inconsistent [method resolution order] (MRO).\n\n## Why is this bad?\n\nClasses with an inconsistent MRO will raise a `TypeError` at runtime.\n\n## Examples\n\n```python\nclass A: ...\n\n\nclass B(A): ...\n\n\n# TypeError: Cannot create a consistent method resolution order\nclass C(A, B): ... # error\n```\n\n[method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "index-out-of-bounds": { |
| "title": "detects index out of bounds errors", |
| "description": "## What it does\n\nChecks for attempts to use an out of bounds index to get an item from a container.\n\n## Why is this bad?\n\nUsing an out of bounds index will raise an `IndexError` at runtime.\n\n## Examples\n\n```python\nt = (0, 1, 2)\n# IndexError: tuple index out of range\nt[3] # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "ineffective-final": { |
| "title": "detects calls to `final()` that type checkers cannot interpret", |
| "description": "## What it does\n\nChecks for calls to `final()` that type checkers cannot interpret.\n\n## Why is this bad?\n\nThe `final()` function is designed to be used as a decorator. When called directly as a function\n(e.g., `final(type(...))`), type checkers will not understand the application of `final` and will\nnot prevent subclassing.\n\n## Example\n\n```python\nfrom typing import final\n\n# Incorrect: type checkers will not prevent subclassing\nMyClass = final(type(\"MyClass\", (), {})) # error\n\n\n# Correct: use `final` as a decorator\n@final\nclass MyClass: ...\n```", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "instance-layout-conflict": { |
| "title": "detects class definitions that raise `TypeError` due to instance layout conflict", |
| "description": "## What it does\n\nChecks for classes definitions which will fail at runtime due to \"instance memory layout conflicts\".\n\nThis error is usually caused by attempting to combine multiple classes that define non-empty\n`__slots__` in a class's [Method Resolution Order][method-resolution-order] (MRO), or by attempting\nto combine multiple builtin classes in a class's MRO.\n\n## Why is this bad?\n\nInheriting from bases with conflicting instance memory layouts will lead to a `TypeError` at\nruntime.\n\nAn instance memory layout conflict occurs when CPython cannot determine the memory layout instances\nof a class should have, because the instance memory layout of one of its bases conflicts with the\ninstance memory layout of one or more of its other bases.\n\nFor example, if a Python class defines non-empty `__slots__`, this will impact the memory layout of\ninstances of that class. Multiple inheritance from more than one different class defining non-empty\n`__slots__` is not allowed:\n\n```python\nclass A:\n __slots__ = (\"a\", \"b\")\n\n\nclass B:\n __slots__ = (\"a\", \"b\") # Even if the values are the same\n\n\n# TypeError: multiple bases have instance lay-out conflict\nclass C(A, B): ... # error\n```\n\nAn instance layout conflict can also be caused by attempting to use multiple inheritance with two\nbuiltin classes, due to the way that these classes are implemented in a CPython C extension:\n\n```python\n# TypeError: multiple bases have instance lay-out conflict\nclass A(int, float): ... # error\n```\n\nNote that pure-Python classes with no `__slots__`, or pure-Python classes with empty `__slots__`,\nare always compatible:\n\n```python\nclass A: ...\n\n\nclass B:\n __slots__ = ()\n\n\nclass C:\n __slots__ = (\"a\", \"b\")\n\n\n# fine\nclass D(A, B, C): ...\n```\n\n## Known problems\n\nClasses whose `__slots__` values cannot be determined statically are not always considered disjoint\nbases by ty. Static definitions can include string literals, fixed-length tuples, and literal lists,\nsets, or dictionaries of string literals.\n\nAdditionally, this check is not exhaustive: many C extensions (including several in the standard\nlibrary) define classes that use extended memory layouts and thus cannot coexist in a single MRO.\nSince it is currently not possible to represent this fact in stub files, having a full knowledge of\nthese classes is also impossible. When it comes to classes that do not define `__slots__` at the\nPython level, therefore, ty, currently only hard-codes a number of cases where it knows that a class\nwill produce instances with an atypical memory layout.\n\n## Further reading\n\n- [CPython documentation: `__slots__`](https://docs.python.org/3/reference/datamodel.html#slots)\n- [CPython documentation: Method Resolution Order](https://docs.python.org/3/glossary.html#term-method-resolution-order)\n\n[method-resolution-order]: https://docs.python.org/3/glossary.html#term-method-resolution-order", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-argument-type": { |
| "title": "detects call arguments whose type is not assignable to the corresponding typed parameter", |
| "description": "## What it does\n\nDetects call arguments whose type is not assignable to the corresponding typed parameter.\n\n## Why is this bad?\n\nPassing an argument of a type the function (or callable object) does not accept violates the\nexpectations of the function author and may cause unexpected runtime errors within the body of the\nfunction.\n\n## Examples\n\n```python\ndef func(x: int): ...\n\n\nfunc(\"foo\") # error: [invalid-argument-type]\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-assignment": { |
| "title": "detects invalid assignments", |
| "description": "## What it does\n\nChecks for assignments where the type of the value is not [assignable to] the type of the assignee.\n\n## Why is this bad?\n\nSuch assignments break the rules of the type system and weaken a type checker's ability to\naccurately reason about your code.\n\n## Examples\n\n```python\na: int = \"\" # error\n```\n\n[assignable to]: https://typing.python.org/en/latest/spec/glossary.html#term-assignable", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-attribute-access": { |
| "title": "Invalid attribute access", |
| "description": "## What it does\n\nChecks for assignments to class variables from instances and assignments to instance-only attributes\nfrom their class. Also checks for reads and writes of generic instance attributes through a generic\nclass or a specialized generic alias.\n\nAn \"instance-only\" variable is one which is only ever assigned to or declared when accessed via\n`self` in an instance method.\n\nA generic instance attribute has a type that depends on the class's type parameters. Specializing a\ngeneric class does not create separate class attribute storage, so these attributes cannot be\naccessed through the generic class or a specialized alias. Access through a `type[...]` receiver is\nallowed because it can refer to a concrete subclass with its own class attributes.\n\n## Why is this bad?\n\nIncorrect assignments break the rules of the type system and weaken a type checker's ability to\naccurately reason about your code.\n\n## Examples\n\n```python\nfrom typing import ClassVar\n\n\nclass C:\n instance_var: int\n class_var: ClassVar[int] = 1\n\n def __init__(self):\n # instance variable declared in the class body\n self.instance_var = 42\n\n # instance-only variable not declared in the class body\n self.instance_only_var: int = 42\n\n\nC.class_var = 3 # okay\n\nC.instance_var = 56 # okay\nC().instance_var = 72 # okay\n\nC().instance_only_var = 100 # okay\n\n# Cannot assign to class variable from instance\nC().class_var = 3 # error\n\n# Cannot assign to instance-only variable from class\nC.instance_only_var = 56 # error\n```\n\n```python\nfrom typing import Generic, TypeVar\n\nT = TypeVar(\"T\")\n\n\nclass Box(Generic[T]):\n value: T\n\n\nBox[int].value = 1 # error\nBox.value # error\n\nbox = Box[int]()\nbox.value = 1 # okay\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-attribute-override": { |
| "title": "detects attribute overrides that change class-variable or instance-variable behavior", |
| "description": "## What it does\n\nDetects attribute overrides that change whether an inherited attribute is a class variable or an\ninstance variable.\n\nThis rule currently only covers class-variable and instance-variable category changes.\n\n## Why is this bad?\n\nPure class variables and instance variables have different access and assignment behavior.\nOverriding one with the other violates the\n[Liskov Substitution Principle][liskov-substitution-principle] (\"LSP\"), because code that is valid\nfor the superclass may no longer be valid for the subclass.\n\n## Example\n\n```python\nfrom typing import ClassVar\n\n\nclass Base:\n instance_attr: int\n class_attr: ClassVar[int]\n\n\nclass Sub(Base):\n instance_attr: ClassVar[int] # error: [invalid-attribute-override]\n class_attr: int # error: [invalid-attribute-override]\n```\n\n[liskov-substitution-principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-await": { |
| "title": "detects awaiting on types that don't support it", |
| "description": "## What it does\n\nChecks for `await` being used with types that are not [Awaitable][awaitable-abc].\n\n## Why is this bad?\n\nSuch expressions will lead to `TypeError` being raised at runtime.\n\n## Examples\n\n```python\nimport asyncio\n\n\nclass InvalidAwait:\n def __await__(self) -> int:\n return 5\n\n\nasync def main() -> None:\n await InvalidAwait() # error: [invalid-await]\n await 42 # error: [invalid-await]\n\n\nasyncio.run(main())\n```\n\n[awaitable-abc]: https://docs.python.org/3/library/collections.abc.html#collections.abc.Awaitable", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-base": { |
| "title": "detects class bases that will cause the class definition to raise an exception at runtime", |
| "description": "## What it does\n\nChecks for class definitions that have bases which are not instances of `type`.\n\n## Why is this bad?\n\nClass definitions with bases like this will lead to `TypeError` being raised at runtime.\n\n## Examples\n\n```python\nclass A(42): ... # error: [invalid-base]\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-context-manager": { |
| "title": "detects expressions used in with statements that don't implement the context manager protocol", |
| "description": "## What it does\n\nChecks for expressions used in `with` statements that do not implement the context manager protocol.\n\n## Why is this bad?\n\nSuch a statement will raise `TypeError` at runtime.\n\n## Examples\n\n```python\n# TypeError: 'int' object does not support the context manager protocol\nwith 1: # error\n print(2)\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-dataclass": { |
| "title": "detects invalid `@dataclass` applications", |
| "description": "## What it does\n\nChecks for invalid applications of the `@dataclass` decorator.\n\n## Why is this bad?\n\nApplying `@dataclass` with incompatible arguments raises an exception while creating the class:\n\n- `order=True` with `eq=False`\n- `weakref_slot=True` with `slots=False`\n- `slots=True` when the class already defines `__slots__`\n\nApplying `@dataclass` to a class that inherits from `NamedTuple`, `TypedDict`, `Enum`, or `Protocol`\nis also invalid:\n\n- `NamedTuple` and `TypedDict` classes will raise an exception at runtime when instantiating the\n class.\n- `Enum` classes with `@dataclass` are [explicitly not supported].\n- `Protocol` classes define interfaces and cannot be instantiated.\n\n## Examples\n\n```python\nfrom dataclasses import dataclass\nfrom typing import NamedTuple\n\n\n@dataclass(order=True, eq=False) # error: [invalid-dataclass]\nclass Ordered: ...\n\n\n@dataclass\nclass Foo(NamedTuple): # error: [invalid-dataclass]\n x: int\n```\n\nSee: <https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass>\n\n[explicitly not supported]: https://docs.python.org/3/howto/enum.html#dataclass-support", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-dataclass-override": { |
| "title": "detects dataclasses with `frozen=True` that have a custom `__setattr__` or `__delattr__` implementation", |
| "description": "## What it does\n\nChecks for dataclass definitions that have both `frozen=True` and a custom `__setattr__` or\n`__delattr__` method defined.\n\n## Why is this bad?\n\nFrozen dataclasses synthesize `__setattr__` and `__delattr__` methods which raise a\n`FrozenInstanceError` to emulate immutability.\n\nOverriding either of these methods raises a runtime error.\n\n## Examples\n\n```python\nfrom dataclasses import dataclass\n\n\n@dataclass(frozen=True)\nclass A:\n def __setattr__(self, name: str, value: object) -> None: ... # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-declaration": { |
| "title": "detects invalid declarations", |
| "description": "## What it does\n\nChecks for declarations where the inferred type of an existing symbol is not [assignable to] its\npost-hoc declared type.\n\n## Why is this bad?\n\nSuch declarations break the rules of the type system and weaken a type checker's ability to\naccurately reason about your code.\n\n## Examples\n\n```python\na = 1\na: str # error\n```\n\n[assignable to]: https://typing.python.org/en/latest/spec/glossary.html#term-assignable", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-enum-member-annotation": { |
| "title": "detects type annotations on enum members", |
| "description": "## What it does\n\nChecks for enum members that have explicit type annotations.\n\n## Why is this bad?\n\nThe [typing spec] states that type checkers should infer a literal type for all enum members. An\nexplicit type annotation on an enum member is misleading because the annotated type will be\nincorrect — the actual runtime type is the enum class itself, not the annotated type.\n\nIn CPython's `enum` module, annotated assignments with values are still treated as members at\nruntime, but the annotation will confuse readers of the code.\n\n## Examples\n\n```python\nfrom enum import Enum\n\n\nclass Pet(Enum):\n CAT = 1 # OK\n # enum members should not be annotated\n DOG: int = 2 # error\n```\n\nUse instead:\n\n```python\nfrom enum import Enum\n\n\nclass Pet(Enum):\n CAT = 1\n DOG = 2\n```\n\n## References\n\n- [Typing spec: Enum members](https://typing.python.org/en/latest/spec/enums.html#enum-members)\n\n[typing spec]: https://typing.python.org/en/latest/spec/enums.html#enum-members", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-exception-caught": { |
| "title": "detects exception handlers that catch classes that do not inherit from `BaseException`", |
| "description": "## What it does\n\nChecks for exception handlers that catch non-exception classes.\n\n## Why is this bad?\n\nCatching classes that do not inherit from `BaseException` will raise a `TypeError` at runtime.\n\n## Example\n\n```python\nimport random\n\n\ndef might_raise() -> float:\n return 1 / random.choice([0, 1, 2, 3, 4, 5])\n\n\ntry:\n might_raise()\nexcept 1: # error\n ...\n```\n\nUse instead:\n\n```python\nimport random\n\n\ndef might_raise() -> float:\n return 1 / random.choice([0, 1, 2, 3, 4, 5])\n\n\ntry:\n might_raise()\nexcept ZeroDivisionError:\n ...\n```\n\n## References\n\n- [Python documentation: except clause](https://docs.python.org/3/reference/compound_stmts.html#except-clause)\n- [Python documentation: Built-in Exceptions](https://docs.python.org/3/library/exceptions.html#built-in-exceptions)\n\n## Ruff rule\n\nThis rule corresponds to Ruff's\n[`except-with-non-exception-classes` (`B030`)](https://docs.astral.sh/ruff/rules/except-with-non-exception-classes)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-explicit-override": { |
| "title": "detects methods that are decorated with `@override` but do not override any method in a superclass", |
| "description": "## What it does\n\nChecks for methods that are decorated with `@override` but do not override any method in a\nsuperclass.\n\n## Why is this bad?\n\nDecorating a method with `@override` declares to the type checker that the intention is that it\nshould override a method from a superclass.\n\n## Example\n\n```toml\n[environment]\npython-version = \"3.12\"\n```\n\n```python\nfrom typing import override\n\n\nclass A:\n @override\n def foo(self): ... # error\n\n\nclass B(A):\n @override\n def ffooo(self): ... # error\n\n\nclass C:\n @override\n def __repr__(self): ... # fine: overrides `object.__repr__`\n\n\nclass D(A):\n @override\n def foo(self): ... # fine: overrides `A.foo`\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-frozen-dataclass-subclass": { |
| "title": "detects dataclasses with invalid frozen/non-frozen subclassing", |
| "description": "## What it does\n\nChecks for dataclasses with invalid frozen inheritance:\n\n- A frozen dataclass cannot inherit from a non-frozen dataclass.\n- A non-frozen dataclass cannot inherit from a frozen dataclass.\n\n## Why is this bad?\n\nPython raises a `TypeError` at runtime when either of these inheritance patterns occurs.\n\n## Example\n\n```python\nfrom dataclasses import dataclass\n\n\n@dataclass\nclass Base:\n x: int\n\n\n@dataclass(frozen=True)\nclass Child(Base): # error\n y: int\n\n\n@dataclass(frozen=True)\nclass FrozenBase:\n x: int\n\n\n@dataclass\nclass NonFrozenChild(FrozenBase): # error\n y: int\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-generic-class": { |
| "title": "detects invalid generic classes", |
| "description": "## What it does\n\nChecks for the creation of invalid generic classes\n\n## Why is this bad?\n\nThere are several requirements that you must follow when defining a generic class. Many of these\nresult in `TypeError` being raised at runtime if they are violated.\n\n## Examples\n\n```toml\n[environment]\npython-version = \"3.12\"\n```\n\n```python\nfrom typing_extensions import Generic, TypeVar\n\nT = TypeVar(\"T\")\nU = TypeVar(\"U\", default=int)\n\n\n# class uses both PEP-695 syntax and legacy syntax\nclass C[U](Generic[T]): ... # error\n\n\n# type parameter with default comes before type parameter without default\nclass D(Generic[U, T]): ... # error\n```\n\n## References\n\n- [Typing spec: Generics](https://typing.python.org/en/latest/spec/generics.html#introduction)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-generic-enum": { |
| "title": "detects generic enum classes", |
| "description": "## What it does\n\nChecks for enum classes that are also generic.\n\n## Why is this bad?\n\nEnum classes cannot be generic. Python does not support generic enums: attempting to create one will\neither result in an immediate `TypeError` at runtime, or will create a class that cannot be\nspecialized in the way that a normal generic class can.\n\n## Examples\n\n```toml\n[environment]\npython-version = \"3.12\"\n```\n\n```python\nfrom enum import Enum\nfrom typing import Generic, TypeVar\n\nT = TypeVar(\"T\")\n\n\n# enum class cannot be generic (class creation fails with `TypeError`)\nclass E[T](Enum): # error\n A = 1\n\n\n# enum class cannot be generic (class creation fails with `TypeError`)\nclass F(Enum, Generic[T]): # error\n A = 1\n\n\n# enum class cannot be generic -- the class creation does not immediately fail...\nclass G(Generic[T], Enum): # error\n A = 1\n\n\n# ...but this raises `KeyError`:\nx: G[int]\n```\n\n## References\n\n- [Python documentation: Enum](https://docs.python.org/3/library/enum.html)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-ignore-comment": { |
| "title": "detects ignore comments that use invalid syntax", |
| "description": "## What it does\n\nChecks for `type: ignore` and `ty: ignore` comments that are syntactically incorrect.\n\n## Why is this bad?\n\nA syntactically incorrect ignore comment is probably a mistake and is useless.\n\n## Examples\n\n```py\n# error\na = 20 / 1 # type: ignoree\n```\n\nUse instead:\n\n```py\na = 20 / 0 # type: ignore\n```", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-key": { |
| "title": "detects invalid subscript accesses or TypedDict literal keys", |
| "description": "## What it does\n\nChecks for subscript accesses with invalid keys and `TypedDict` construction with an unknown key.\n\n## Why is this bad?\n\nSubscripting with an invalid key will raise a `KeyError` at runtime.\n\nCreating a `TypedDict` with an unknown key is likely a mistake; if the `TypedDict` is `closed=true`\nit also violates the expectations of the type.\n\n## Examples\n\n```python\nfrom typing import TypedDict\nfrom typing_extensions import NotRequired\n\n\nclass Person(TypedDict):\n name: NotRequired[str]\n age: NotRequired[int]\n\n\nalice = Person(name=\"Alice\", age=30)\n# KeyError: 'height'\nalice[\"height\"] # error\n\n# error\nbob: Person = {\"nickname\": \"Bob\", \"age\": 30} # typo!\n\n# error\ncarol = Person(name=\"Carol\", aeg=25) # typo!\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-legacy-positional-parameter": { |
| "title": "detects incorrect usage of the legacy convention for specifying positional-only parameters", |
| "description": "## What it does\n\nChecks for parameters that appear to be attempting to use the legacy convention to specify that a\nparameter is positional-only, but do so incorrectly.\n\nThe \"legacy convention\" for specifying positional-only parameters was specified in\n[PEP 484][pep-484]. It states that parameters with names starting with `__` should be considered\npositional-only by type checkers. [PEP 570][pep-570], introduced in Python 3.8, added dedicated\nsyntax for specifying positional-only parameters, rendering the legacy convention obsolete. However,\nsome codebases may still use the legacy convention for compatibility with older Python versions.\n\n## Why is this bad?\n\nIn most cases, a type checker will not consider a parameter to be positional-only if it comes after\na positional-or-keyword parameter, even if its name starts with `__`. This may be unexpected to the\nauthor of the code.\n\n## Example\n\n```python\n# `__y` is not considered positional-only\ndef f(x, __y): # error\n pass\n```\n\nUse instead:\n\n```python\ndef f(__x, __y): # If you need compatibility with Python <=3.7\n pass\n```\n\nor:\n\n```python\ndef f(x, y, /): # Python 3.8+ syntax\n pass\n```\n\n## References\n\n- [Typing spec: positional-only parameters (legacy syntax)](https://typing.python.org/en/latest/spec/historical.html#pos-only-double-underscore)\n- [Python glossary: parameters](https://docs.python.org/3/glossary.html#term-parameter)\n\n[pep-484]: https://peps.python.org/pep-0484/#positional-only-arguments\n[pep-570]: https://peps.python.org/pep-0570/", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-legacy-type-variable": { |
| "title": "detects invalid legacy type variables", |
| "description": "## What it does\n\nChecks for the creation of invalid legacy `TypeVar`s\n\n## Why is this bad?\n\nThere are several requirements that you must follow when creating a legacy `TypeVar`.\n\n## Examples\n\n```python\nfrom typing import TypeVar\n\nT = TypeVar(\"T\") # okay\nT = TypeVar(\"T\") # error: \"Cannot redefine `T` as a type variable\"\n\n\n# TypeVar must be immediately assigned to a variable\n# error\ndef f(t: TypeVar(\"U\")): ... # ty: ignore[invalid-type-form]\n```\n\n## References\n\n- [Typing spec: Generics](https://typing.python.org/en/latest/spec/generics.html#introduction)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-match-pattern": { |
| "title": "detect invalid match patterns", |
| "description": "## What it does\n\nChecks for invalid match patterns.\n\n## Why is this bad?\n\nInvalid match patterns can cause a `TypeError` at runtime. This includes:\n\n- Using a non-type object in a class pattern.\n- Providing positional subpatterns when `__match_args__` is missing or has an invalid static type.\n- Matching against `collections.abc.Callable` with positional subpatterns.\n- Matching against a non-runtime-checkable protocol.\n- Matching against a `TypedDict`.\n\n## Examples\n\n```python\nclass Point:\n __match_args__ = (\"x\", \"y\")\n\n\ndef describe(p: Point) -> None:\n match p:\n # TypeError at runtime: Point() accepts 2 positional sub-patterns (3 given)\n case Point(x, y, z): # error: [invalid-match-pattern]\n ...\n```\n\n```python\nNotAClass = 42\n\nmatch object():\n # TypeError at runtime: called match pattern must be a class\n case NotAClass(): # error: [invalid-match-pattern]\n ...\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-metaclass": { |
| "title": "detects invalid `metaclass=` arguments", |
| "description": "## What it does\n\nChecks for arguments to `metaclass=` that are invalid.\n\n## Why is this bad?\n\nPython allows arbitrary expressions to be used as the argument to `metaclass=`. These expressions,\nhowever, need to be callable and accept the same arguments as `type.__new__`.\n\n## Example\n\n```python\n# TypeError: 'int' object is not callable\nclass B(metaclass=42): ... # error\n```\n\n## References\n\n- [Python documentation: Metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-method-override": { |
| "title": "detects method definitions that violate the Liskov Substitution Principle", |
| "description": "## What it does\n\nDetects method overrides that violate the\n[Liskov Substitution Principle][liskov-substitution-principle] (\"LSP\").\n\nThe LSP states that an instance of a subtype should be substitutable for an instance of its\nsupertype. Applied to Python, this means:\n\n1. All argument combinations a superclass method accepts must also be accepted by an overriding\n subclass method.\n1. The return type of an overriding subclass method must be a subtype of the return type of the\n superclass method.\n\n## Why is this bad?\n\nViolating the Liskov Substitution Principle will lead to many of ty's assumptions and inferences\nbeing incorrect, which will mean that it will fail to catch many possible type errors in your code.\n\n## Example\n\n```python\nclass Super:\n def method(self, x) -> int:\n return 42\n\n\nclass Sub(Super):\n # Liskov violation: `str` is not a subtype of `int`,\n # but the supertype method promises to return an `int`.\n def method(self, x) -> str: # error: [invalid-method-override]\n return \"foo\"\n\n\ndef accepts_super(s: Super) -> int:\n return s.method(x=42)\n\n\n# The result of this call is a string, but ty will infer it to be an `int`\n# due to the violation of the Liskov Substitution Principle.\naccepts_super(Sub())\n\n\nclass Sub2(Super):\n # Liskov violation: the superclass method can be called with a `x=`\n # keyword argument, but the subclass method does not accept it.\n def method(self, y) -> int: # error: [invalid-method-override]\n return 42\n\n\n# TypeError at runtime: method() got an unexpected keyword argument 'x'\n# ty cannot catch this error due to the violation of the Liskov Substitution Principle.\naccepts_super(Sub2())\n```\n\n## Common issues\n\n### Why does ty complain about my `__eq__` method?\n\n`__eq__` and `__ne__` methods in Python are generally expected to accept arbitrary objects as their\nsecond argument, for example:\n\n```python\nclass A:\n x: int\n\n def __eq__(self, other: object) -> bool:\n # gracefully handle an object of an unexpected type\n # without raising an exception\n if not isinstance(other, A):\n return False\n return self.x == other.x\n```\n\nIf `A.__eq__` here were annotated as only accepting `A` instances for its second argument, it would\nimply that you wouldn't be able to use `==` between instances of `A` and instances of unrelated\nclasses without an exception possibly being raised. While some classes in Python do indeed behave\nthis way, the strongly held convention is that it should be avoided wherever possible. As part of\nthis check, therefore, ty enforces that `__eq__` and `__ne__` methods accept `object` as their\nsecond argument.\n\n### Why does ty disagree with Ruff about how to write my method?\n\nRuff has several rules that will encourage you to rename a parameter, or change its type signature,\nif it thinks you're falling into a certain anti-pattern. For example, Ruff's\n[ARG002](https://docs.astral.sh/ruff/rules/unused-method-argument/) rule recommends that an unused\nparameter should either be removed or renamed to start with `_`. Applying either of these\nsuggestions can cause ty to start reporting an `invalid-method-override` error if the function in\nquestion is a method on a subclass that overrides a method on a superclass, and the change would\ncause the subclass method to no longer accept all argument combinations that the superclass method\naccepts.\n\nThis can usually be resolved by adding [`@typing.override`][override] to your method definition.\nRuff knows that a method decorated with `@typing.override` is intended to override a method by the\nsame name on a superclass, and avoids reporting rules like ARG002 for such methods; it knows that\nthe changes recommended by ARG002 would violate the Liskov Substitution Principle.\n\nCorrect use of `@override` is enforced by ty's `invalid-explicit-override` rule.\n\n[liskov-substitution-principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle\n[override]: https://docs.python.org/3/library/typing.html#typing.override", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-module-getattr-call": { |
| "title": "detects imports that fail while calling module-level `__getattr__`", |
| "description": "## What it does\n\nChecks for imports that fail when calling a module-level `__getattr__` function.\n\n## Why is this bad?\n\nIf a module defines `__getattr__`, Python calls it when a `from` import requests a name that is not\notherwise defined. The import raises an exception if `__getattr__` cannot accept the requested name.\n\n## Examples\n\n`module.py`:\n\n```python\ndef __getattr__() -> str:\n return \"fallback\"\n```\n\n`main.py`:\n\n```python\n# TypeError: __getattr__() takes 0 positional arguments but 1 was given\nfrom module import missing # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-named-tuple": { |
| "title": "detects invalid `NamedTuple` class definitions", |
| "description": "## What it does\n\nChecks for invalidly defined `NamedTuple` classes.\n\n## Why is this bad?\n\nAn invalidly defined `NamedTuple` class may lead to the type checker drawing incorrect conclusions.\nIt may also lead to `TypeError`s or `AttributeError`s at runtime.\n\n## Examples\n\nA class definition cannot combine `NamedTuple` with other base classes in multiple inheritance;\ndoing so raises a `TypeError` at runtime. The sole exception to this rule is `Generic[]`, which can\nbe used alongside `NamedTuple` in a class's bases list.\n\n```pycon\n>>> from typing import NamedTuple\n>>> class Foo(NamedTuple, object): ...\nTypeError: can only inherit from a NamedTuple type and Generic\n```\n\nFurther, `NamedTuple` field names cannot start with an underscore:\n\n```pycon\n>>> from typing import NamedTuple\n>>> class Foo(NamedTuple):\n... _bar: int\nValueError: Field names cannot start with an underscore: '_bar'\n```\n\n`NamedTuple` classes also have certain synthesized attributes (like `_asdict`, `_make`, `_replace`,\netc.) that cannot be overwritten. Attempting to assign to these attributes without a type annotation\nwill raise an `AttributeError` at runtime.\n\n```pycon\n>>> from typing import NamedTuple\n>>> class Foo(NamedTuple):\n... x: int\n... _asdict = 42\nAttributeError: Cannot overwrite NamedTuple attribute _asdict\n```\n\nFinally, `NamedTuple` field annotations cannot use the `ClassVar` or `Final` type qualifiers. These\nqualifiers also cause a runtime error when annotations are evaluated eagerly:\n\n```pycon\n>>> from typing import ClassVar, NamedTuple\n>>> class Foo(NamedTuple):\n... x: ClassVar[int]\nTypeError: typing.ClassVar[int] is not valid as type argument\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-named-tuple-override": { |
| "title": "detects subclass members that override inherited `NamedTuple` fields", |
| "description": "## What it does\n\nChecks for subclass members that override inherited `NamedTuple` fields.\n\n## Why is this bad?\n\nReusing an inherited `NamedTuple` field name in a subclass creates a class where tuple indexing and\n`repr()` still reflect the original field, while attribute access follows the subclass member.\n\n## Default level\n\nThis rule is a warning by default because these overrides do not make the class invalid at runtime.\n\n## Examples\n\n```python\nfrom typing import NamedTuple\n\n\nclass User(NamedTuple):\n name: str\n\n\nclass Admin(User):\n name = \"shadowed\" # error: [invalid-named-tuple-override]\n\n\nadmin = Admin(\"Alice\")\nadmin.name # \"shadowed\"\nadmin[0] # \"Alice\"\n```", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-newtype": { |
| "title": "detects invalid NewType definitions", |
| "description": "## What it does\n\nChecks for the creation of invalid `NewType`s\n\n## Why is this bad?\n\nThere are several requirements that you must follow when creating a `NewType`.\n\n## Examples\n\n```python\nfrom typing import NewType\n\n\ndef get_name() -> str:\n return \"name\"\n\n\nFoo = NewType(\"Foo\", int) # okay\n# The first argument to `NewType` must be a string literal\nBar = NewType(get_name(), int) # error\n# invalid base for `typing.NewType`\nBaz = NewType(\"Baz\", int | str) # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-overload": { |
| "title": "detects invalid `@overload` usages", |
| "description": "## What it does\n\nChecks for various invalid `@overload` usages.\n\n## Why is this bad?\n\nThe `@overload` decorator is used to define functions and methods that accepts different\ncombinations of arguments and return different types based on the arguments passed. This is mainly\nbeneficial for type checkers. But, if the `@overload` usage is invalid, the type checker may not be\nable to provide correct type information.\n\n## Examples\n\n### Single overload\n\n```py\nfrom typing import overload\n\n\n@overload\ndef foo(x: int) -> int: ... # error\ndef foo(x: int | None) -> int | None:\n return x\n```\n\n### Missing implementation\n\n```py\nfrom typing import overload\n\n\n@overload\ndef foo() -> None: ... # error\n@overload\ndef foo(x: int) -> int: ...\n```\n\n## References\n\n- [Python documentation: `@overload`](https://docs.python.org/3/library/typing.html#typing.overload)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-parameter-default": { |
| "title": "detects default values that can't be assigned to the parameter's annotated type", |
| "description": "## What it does\n\nChecks for default values that can't be assigned to the parameter's annotated type.\n\n## Why is this bad?\n\nThis breaks the rules of the type system and weakens a type checker's ability to accurately reason\nabout your code.\n\n## Examples\n\n```python\ndef f(a: int = \"\"): ... # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-paramspec": { |
| "title": "detects invalid ParamSpec usage", |
| "description": "## What it does\n\nChecks for the creation of invalid `ParamSpec`s\n\n## Why is this bad?\n\nThere are several requirements that you must follow when creating a `ParamSpec`.\n\n## Examples\n\n```python\nfrom typing import ParamSpec\n\nP1 = ParamSpec(\"P1\") # okay\n# ParamSpec requires a name\nP2 = ParamSpec() # error\n```\n\n## References\n\n- [Typing spec: ParamSpec](https://typing.python.org/en/latest/spec/generics.html#paramspec)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-protocol": { |
| "title": "detects invalid protocol class definitions", |
| "description": "## What it does\n\nChecks for protocol classes that are invalid at runtime or do not satisfy the typing specification.\n\n## Why is this bad?\n\nAn invalidly defined protocol class may lead to the type checker inferring unexpected things or\naccepting unsafe operations. Some invalid protocol definitions also raise `TypeError` at runtime.\n\n## Examples\n\nA `Protocol` class cannot inherit from a non-`Protocol` class; this raises a `TypeError` at runtime:\n\n```pycon\n>>> from typing import Protocol\n>>> class Foo(int, Protocol): ...\nTraceback (most recent call last):\n File \"<python-input-1>\", line 1, in <module>\n class Foo(int, Protocol): ...\nTypeError: Protocols can only inherit from other protocols, got <class 'int'>\n```\n\nA generic protocol's declared type-variable variance must match how that variable is used by its\nprotocol members. For example, a type variable that appears only in a method's return type must be\ncovariant:\n\n```py\nfrom typing import Protocol, TypeVar\n\nT = TypeVar(\"T\")\n\n\nclass Source(Protocol[T]): # error: [invalid-protocol]\n def read(self) -> T: ...\n```\n\nAlthough Python constructs this protocol successfully at runtime, it is invalid for static typing.\nDeclare the type variable with `TypeVar(\"T\", covariant=True)` instead.", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-raise": { |
| "title": "detects `raise` statements that raise invalid exceptions or use invalid causes", |
| "description": "Checks for `raise` statements that raise non-exceptions or use invalid causes for their raised\nexceptions.\n\n## Why is this bad?\n\nOnly subclasses or instances of `BaseException` can be raised. For an exception's cause, the same\nrules apply, except that `None` is also permitted. Violating these rules results in a `TypeError` at\nruntime.\n\n## Examples\n\n```python\ndef something():\n raise NameError\n\n\ndef cause() -> None:\n pass\n\n\ndef f():\n try:\n something()\n except NameError:\n # error: \"Cannot raise object of type `Literal[\"oops!\"]`\"\n # error: \"Cannot use object of type `def cause() -> None` as an exception cause\"\n raise \"oops!\" from cause\n\n\ndef g():\n # error: \"Cannot raise `NotImplemented`\"\n # error: \"Cannot use object of type `Literal[42]` as an exception cause\"\n raise NotImplemented from 42\n```\n\nUse instead:\n\n```python\ndef something():\n raise NameError\n\n\ndef f():\n try:\n something()\n except NameError as e:\n raise RuntimeError(\"oops!\") from e\n\n\ndef g():\n raise NotImplementedError from None\n```\n\n## References\n\n- [Python documentation: The `raise` statement](https://docs.python.org/3/reference/simple_stmts.html#raise)\n- [Python documentation: Built-in Exceptions](https://docs.python.org/3/library/exceptions.html#built-in-exceptions)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-return-type": { |
| "title": "detects returned values that can't be assigned to the function's annotated return type", |
| "description": "## What it does\n\nDetects returned values that can't be assigned to the function's annotated return type.\n\nNote that the special case of a function with a non-`None` return type and an empty body is handled\nby the separate `empty-body` error code.\n\n## Why is this bad?\n\nReturning an object of a type incompatible with the annotated return type is unsound, and will lead\nto ty inferring incorrect types elsewhere.\n\n## Examples\n\n```python\ndef func() -> int:\n return \"a\" # error: [invalid-return-type]\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-super-argument": { |
| "title": "detects invalid arguments for `super()`", |
| "description": "## What it does\n\nDetects `super()` calls where:\n\n- the first argument is not a valid class literal, or\n- the second argument is not an instance or subclass of the first argument.\n\n## Why is this bad?\n\n`super(type, obj)` expects:\n\n- the first argument to be a class,\n- and the second argument to satisfy one of the following:\n - `isinstance(obj, type)` is `True`\n - `issubclass(obj, type)` is `True`\n\nViolating this relationship will raise a `TypeError` at runtime.\n\n## Examples\n\n```python\nclass A: ...\n\n\nclass B(A): ...\n\n\nsuper(A, B()) # it's okay! `A` satisfies `isinstance(B(), A)`\n\n# `A()` is not a class\nsuper(A(), B()) # error\n\n# `A()` does not satisfy `isinstance(A(), B)`\nsuper(B, A()) # error\n# `A` does not satisfy `issubclass(A, B)`\nsuper(B, A) # error\n```\n\n## References\n\n- [Python documentation: super()](https://docs.python.org/3/library/functions.html#super)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-syntax-in-forward-annotation": { |
| "title": "detects invalid syntax in forward annotations", |
| "description": "## What it does\n\nChecks for string-literal annotations where the string cannot be parsed as a Python expression.\n\n## Why is this bad?\n\nType annotations are expected to be Python expressions that describe the expected type of a\nvariable, parameter, attribute or `return` statement.\n\nType annotations are permitted to be string-literal expressions, in order to enable forward\nreferences to names not yet defined. However, it must be possible to parse the contents of that\nstring literal as a normal Python expression.\n\n## Example\n\n```python\ndef foo() -> \"instance of C\": # error\n return 42\n\n\nclass C: ...\n```\n\nUse instead:\n\n```python\ndef foo() -> \"C\":\n return C()\n\n\nclass C: ...\n```\n\n## References\n\n- [Typing spec: The meaning of annotations](https://typing.python.org/en/latest/spec/annotations.html#the-meaning-of-annotations)\n- [Typing spec: String annotations](https://typing.python.org/en/latest/spec/annotations.html#string-annotations)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-total-ordering": { |
| "title": "detects `@total_ordering` classes without an ordering method", |
| "description": "## What it does\n\nChecks for classes decorated with `@functools.total_ordering` that don't define any ordering method\n(`__lt__`, `__le__`, `__gt__`, or `__ge__`).\n\n## Why is this bad?\n\nThe `@total_ordering` decorator requires the class to define at least one ordering method. If none\nis defined, Python raises a `ValueError` at runtime.\n\n## Example\n\n```python\nfrom functools import total_ordering\n\n\n# no ordering method defined\n@total_ordering # error\nclass MyClass:\n def __eq__(self, other: object) -> bool:\n return True\n```\n\nUse instead:\n\n```python\nfrom functools import total_ordering\n\n\n@total_ordering\nclass MyClass:\n def __eq__(self, other: object) -> bool:\n return True\n\n def __lt__(self, other: \"MyClass\") -> bool:\n return True\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-type-alias-type": { |
| "title": "detects invalid TypeAliasType definitions", |
| "description": "## What it does\n\nChecks for the creation of invalid `TypeAliasType`s\n\n## Why is this bad?\n\nThere are several requirements that you must follow when creating a `TypeAliasType`.\n\n## Examples\n\n```toml\n[environment]\npython-version = \"3.12\"\n```\n\n```python\nfrom typing import TypeAliasType, TypeVar\n\n\ndef get_name() -> str:\n return \"NewAlias\"\n\n\nIntOrStr = TypeAliasType(\"IntOrStr\", int | str) # okay\n# TypeAliasType name must be a string literal\nNewAlias = TypeAliasType(get_name(), int) # error\n\nT = TypeVar(\"T\")\nGenericAlias = TypeAliasType(\"GenericAlias\", list[T], type_params=(T,)) # okay\n# TypeAliasType type parameters must be type variables\nInvalidAlias = TypeAliasType(\"InvalidAlias\", list[T], type_params=(list[T],)) # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-type-arguments": { |
| "title": "detects invalid type arguments in generic specialization", |
| "description": "## What it does\n\nChecks for invalid type arguments in explicit type specialization.\n\n## Why is this bad?\n\nProviding the wrong number of type arguments or type arguments that don't satisfy the type\nvariable's bounds or constraints will lead to incorrect type inference and may indicate a\nmisunderstanding of the generic type's interface.\n\n## Examples\n\nUsing legacy type variables:\n\n```toml\n[environment]\npython-version = \"3.12\"\n```\n\n```python\nfrom typing import Generic, TypeVar\n\nT1 = TypeVar(\"T1\", int, str)\nT2 = TypeVar(\"T2\", bound=int)\n\n\nclass Foo1(Generic[T1]): ...\n\n\nclass Foo2(Generic[T2]): ...\n\n\n# bytes does not satisfy T1's constraints\nFoo1[bytes] # error\n# str does not satisfy T2's bound\nFoo2[str] # error\n```\n\nUsing PEP 695 type variables:\n\n```python\nclass Foo[T]: ...\n\n\nclass Bar[T, U]: ...\n\n\n# too many arguments\nFoo[int, str] # error\n# too few arguments\nBar[int] # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-type-checking-constant": { |
| "title": "detects invalid `TYPE_CHECKING` constant assignments", |
| "description": "## What it does\n\nChecks for a value other than `False` assigned to the `TYPE_CHECKING` variable, or an annotation not\nassignable from `bool`.\n\n## Why is this bad?\n\nThe name `TYPE_CHECKING` is reserved for a flag that can be used to provide conditional code seen\nonly by the type checker, and not at runtime. Normally this flag is imported from `typing` or\n`typing_extensions`, but it can also be defined locally. If defined locally, it must be assigned the\nvalue `False` at runtime; the type checker will consider its value to be `True`. If annotated, it\nmust be annotated as a type that can accept `bool` values.\n\n## Examples\n\n```python\nTYPE_CHECKING: str # error\nTYPE_CHECKING = \"\" # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-type-form": { |
| "title": "detects invalid type forms", |
| "description": "## What it does\n\nChecks for expressions that are used as [type expressions] but cannot validly be interpreted as\nsuch.\n\n## Why is this bad?\n\nSuch expressions cannot be understood by ty. In some cases, they might raise errors at runtime.\n\n## Examples\n\n```python\nfrom typing import Annotated\n\n# Int literals are not allowed in this context in type expressions\na: list[1] # error\n# `Annotated` expects at least two arguments\nb: Annotated[int] # error\n```\n\n[type expressions]: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-type-guard-definition": { |
| "title": "detects malformed type guard functions", |
| "description": "## What it does\n\nChecks for type guard functions without a first non-self-like non-keyword-only non-variadic\nparameter.\n\n## Why is this bad?\n\nType narrowing functions must accept at least one positional argument (non-static methods must\naccept another in addition to `self`/`cls`).\n\nExtra parameters/arguments are allowed but do not affect narrowing.\n\n## Examples\n\n```toml\n[environment]\npython-version = \"3.13\"\n```\n\n```python\nfrom typing import TypeIs\n\n\n# no parameter\ndef f() -> TypeIs[int]: # error\n return True\n\n\n# no positional arguments allowed\ndef f(*, v: object) -> TypeIs[int]: # error\n return True\n\n\n# expected variadic arguments\ndef f(*args: object) -> TypeIs[int]: # error\n return True\n\n\nclass C:\n # only positional argument is `self`\n def f(self) -> TypeIs[int]: # error\n return True\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-type-variable-bound": { |
| "title": "detects invalid type variable bounds", |
| "description": "## What it does\n\nChecks for [type variables][type variable] whose bounds reference type variables.\n\n## Why is this bad?\n\nThe bound of a type variable must be a concrete type.\n\n## Examples\n\n```toml\n[environment]\npython-version = \"3.12\"\n```\n\n```python\nfrom typing import TypeVar\n\n# error: [invalid-type-variable-bound]\nRecursiveT = TypeVar(\"RecursiveT\", bound=list[\"RecursiveT\"])\nU = TypeVar(\"U\")\n# error: [invalid-type-variable-bound]\nBoundT = TypeVar(\"BoundT\", bound=U)\n\n\ndef f[T: list[T]](): ... # error: [invalid-type-variable-bound]\ndef g[U, T: U](): ... # error: [invalid-type-variable-bound]\n```\n\n[type variable]: https://docs.python.org/3/library/typing.html#typing.TypeVar", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-type-variable-constraints": { |
| "title": "detects invalid type variable constraints", |
| "description": "## What it does\n\nChecks for constrained [type variables] with only one constraint, or that those constraints\nreference type variables.\n\n## Why is this bad?\n\nA constrained type variable must have at least two constraints.\n\n## Examples\n\n```toml\n[environment]\npython-version = \"3.12\"\n```\n\n```python\nfrom typing import TypeVar\n\nI = TypeVar(\"I\", bound=int)\n# constraint references `I`\nS = TypeVar(\"S\", list[I], int) # error\n\n\n# a constrained type variable needs at least two constraints\ndef f[T: (int,)](): ... # error\n```\n\nUse instead:\n\n```python\nfrom typing import TypeVar\n\nU = TypeVar(\"U\", str, int) # valid constrained TypeVar\n\n# or\n\nT = TypeVar(\"T\", bound=str) # valid bound TypeVar\n\nV = TypeVar(\"V\", list[int], int) # valid constrained Type\n```\n\n[type variables]: https://docs.python.org/3/library/typing.html#typing.TypeVar", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-type-variable-default": { |
| "title": "detects invalid type variable defaults", |
| "description": "## What it does\n\nChecks for [type variables] whose default type is not compatible with the type variable's bound or\nconstraints.\n\n## Why is this bad?\n\nIf a type variable has a bound, the default must be assignable to that bound (see: [bound rules]).\nIf a type variable has constraints, the default must be one of the constraints (see:\n[constraint rules]).\n\n## Examples\n\n```toml\n[environment]\npython-version = \"3.13\"\n```\n\n```python\nfrom typing import TypeVar\n\nT = TypeVar(\"T\", bound=str, default=int) # error: [invalid-type-variable-default]\nU = TypeVar(\"U\", int, str, default=bytes) # error: [invalid-type-variable-default]\n```\n\n[bound rules]: https://typing.python.org/en/latest/spec/generics.html#bound-rules\n[constraint rules]: https://typing.python.org/en/latest/spec/generics.html#constraint-rules\n[type variables]: https://docs.python.org/3/library/typing.html#typing.TypeVar", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-typed-dict-field": { |
| "title": "detects invalid `TypedDict` field declarations", |
| "description": "## What it does\n\nDetects invalid `TypedDict` field declarations.\n\n## Why is this bad?\n\n`TypedDict` subclasses cannot redefine inherited fields incompatibly. Doing so breaks the subtype\nguarantees that `TypedDict` inheritance is meant to preserve.\n\n## Example\n\n```python\nfrom typing import TypedDict\n\n\nclass Base(TypedDict):\n x: int\n\n\nclass Child(Base):\n x: str # error: [invalid-typed-dict-field]\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-typed-dict-header": { |
| "title": "detects invalid statements in `TypedDict` class headers", |
| "description": "## What it does\n\nDetects errors in `TypedDict` class headers, such as unexpected arguments or invalid base classes.\n\n## Why is this bad?\n\nThe typing spec states that `TypedDict`s are not permitted to have custom metaclasses. Using `**`\nunpacking in a `TypedDict` header is also prohibited by ty, as it means that ty cannot statically\ndetermine whether keys in the `TypedDict` are intended to be required or optional.\n\n## Example\n\n```python\nfrom typing import TypedDict\n\n\nclass Meta(type): ...\n\n\nclass Foo(TypedDict, metaclass=Meta): # error: [invalid-typed-dict-header]\n ...\n\n\ndef f(options: dict[str, object]):\n class Bar(TypedDict, **options): # error: [invalid-typed-dict-header]\n ...\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-typed-dict-statement": { |
| "title": "detects invalid statements in `TypedDict` class bodies", |
| "description": "## What it does\n\nDetects statements other than annotated declarations in `TypedDict` class bodies.\n\n## Why is this bad?\n\n`TypedDict` class bodies aren't allowed to contain any other types of statements. For example,\nmethod definitions and field values aren't allowed. None of these will be available on \"instances of\nthe `TypedDict`\" at runtime (as `dict` is the runtime class of all \"`TypedDict` instances\").\n\n## Example\n\n```python\nfrom typing import TypedDict\n\n\nclass Foo(TypedDict):\n def bar(self): # error: [invalid-typed-dict-statement]\n pass\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "invalid-yield": { |
| "title": "detects yield expressions where the \"yield\" or \"send\" type is incompatible with the annotated return type", |
| "description": "## What it does\n\nDetects `yield` and `yield from` expressions where the \"yield\" or \"send\" type is incompatible with\nthe generator function's annotated return type.\n\n## Why is this bad?\n\nYielding a value of a type that doesn't match the generator's declared yield type, or using\n`yield from` with a sub-iterator whose yield or send type is incompatible, is a type error that may\ncause downstream consumers of the generator to receive values of an unexpected type.\n\n## Examples\n\n```python\nfrom typing import Iterator\n\n\ndef gen() -> Iterator[int]:\n yield \"not an int\" # error: [invalid-yield]\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "isinstance-against-protocol": { |
| "title": "reports invalid runtime checks against protocol classes", |
| "description": "## What it does\n\nReports invalid runtime checks against `Protocol` classes. This includes explicit calls\n`isinstance()`/`issubclass()` against non-runtime-checkable protocols, `issubclass()` calls against\nprotocols that have non-method members, and implicit `isinstance()` checks against\nnon-runtime-checkable protocols via pattern matching.\n\n## Why is this bad?\n\nThese calls (implicit or explicit) raise `TypeError` at runtime.\n\n## Examples\n\n```python\nfrom typing_extensions import Protocol, runtime_checkable\n\n\nclass HasX(Protocol):\n x: int\n\n\n@runtime_checkable\nclass HasY(Protocol):\n y: int\n\n\ndef f(arg: object, arg2: type):\n # not runtime-checkable\n isinstance(arg, HasX) # error: [isinstance-against-protocol]\n # not runtime-checkable\n issubclass(arg2, HasX) # error: [isinstance-against-protocol]\n\n\ndef g(arg: object):\n match arg:\n # not runtime-checkable\n case HasX(): # error: [isinstance-against-protocol]\n pass\n\n\ndef h(arg2: type):\n isinstance(arg2, HasY) # fine (runtime-checkable)\n\n # `HasY` is runtime-checkable, but has non-method members,\n # so it still can't be used in `issubclass` checks)\n issubclass(arg2, HasY) # error: [isinstance-against-protocol]\n```\n\n## References\n\n- [Typing documentation: `@runtime_checkable`](https://docs.python.org/3/library/typing.html#typing.runtime_checkable)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "isinstance-against-typed-dict": { |
| "title": "reports runtime checks against `TypedDict` classes", |
| "description": "## What it does\n\nReports runtime checks against `TypedDict` classes. This includes explicit calls to\n`isinstance()`/`issubclass()` and implicit checks performed by `match` class patterns.\n\n## Why is this bad?\n\nUsing a `TypedDict` class in these contexts raises `TypeError` at runtime.\n\n## Examples\n\n```python\nfrom typing_extensions import TypedDict\n\n\nclass Movie(TypedDict):\n name: str\n director: str\n\n\ndef f(arg: object, arg2: type):\n isinstance(arg, Movie) # error: [isinstance-against-typed-dict]\n issubclass(arg2, Movie) # error: [isinstance-against-typed-dict]\n\n\ndef g(arg: object):\n match arg:\n case Movie(): # error: [isinstance-against-typed-dict]\n pass\n```\n\n## References\n\n- [Typing specification: `TypedDict`](https://typing.python.org/en/latest/spec/typeddict.html)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "mismatched-type-name": { |
| "title": "detects functional typing definitions whose declared name does not match the assigned variable", |
| "description": "## What it does\n\nChecks for functional typing definitions whose declared name does not match the variable they are\nassigned to.\n\n## Why is this bad?\n\nConstructors like `TypeVar`, `ParamSpec`, `NewType`, `NamedTuple`, `TypedDict`, and `TypeAliasType`\nall take a name argument that is normally expected to match the assigned variable. A mismatch is\nusually a typo and makes later diagnostics harder to understand.\n\n## Default level\n\nThis rule is a warning by default because ty can usually recover and continue understanding the\nresulting type.\n\n## Examples\n\n```python\nfrom typing import NewType, ParamSpec, TypeVar\nfrom typing_extensions import TypedDict\n\nT = TypeVar(\"U\") # error: [mismatched-type-name]\nP = ParamSpec(\"Q\") # error: [mismatched-type-name]\nUserId = NewType(\"Id\", int) # error: [mismatched-type-name]\nMovie = TypedDict(\"Film\", {\"title\": str}) # error: [mismatched-type-name]\n```", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "missing-argument": { |
| "title": "detects missing required arguments in a call", |
| "description": "## What it does\n\nChecks for missing required arguments in a call.\n\n## Why is this bad?\n\nFailing to provide a required argument will raise a `TypeError` at runtime.\n\n## Examples\n\n```python\ndef func(x: int): ...\n\n\n# TypeError: func() missing 1 required positional argument: 'x'\nfunc() # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "missing-direct-dependency": { |
| "title": "detects imports of dependencies that are not declared directly", |
| "description": "## What it does\n\nChecks for imports from installable packages that the current project or PEP 723 script does not\ndeclare as direct dependencies.\n\nThe name used in dependency declarations can differ from the import name: for example, the `pillow`\npackage is imported as `PIL`.\n\n## Why is this bad?\n\nA dependency can be installed because another package requires it. Importing that dependency without\ndeclaring it makes your code rely on another package's dependency list. If that package removes the\ndependency, your imports can fail.\n\nDeclare the packages that provide your imports in `project.dependencies` or\n`project.optional-dependencies` in `pyproject.toml`. Non-package files, such as tests and\ndevelopment scripts, can also use dependencies declared in dependency groups.\n\nSee uv's [guide to managing dependencies](https://docs.astral.sh/uv/concepts/projects/dependencies/)\nfor how to add these declarations.\n\n## Rule status\n\nThis rule is disabled by default and requires uv integration.\n\nFor projects, enable uv workspace integration (`TY_UV=1`) and use an existing, synchronized\nenvironment. Running [`uv check`](https://docs.astral.sh/uv/reference/cli/#uv-check) synchronizes\nthe environment automatically before invoking ty, unless `--no-sync` is passed. For these checks, ty\nreads the dependency graph and module ownership returned by `uv workspace metadata` without changing\ninstalled packages. uv may update the lockfile to match the current dependency declarations. uv\n0.12.3 or later is required.\n\nFor PEP 723 scripts, enable uv script integration with `TY_UV=scripts` or `TY_UV=1`. ty synchronizes\neach script's environment and checks imports against its inline `dependencies` list. Declarations\nand environments from the enclosing workspace or other scripts do not apply.\n\n## Known limitations\n\nThe current workspace integration applies to directory checks. Explicit file arguments and\n`--config-file` bypass uv workspace discovery.\n\nImports guarded by `TYPE_CHECKING` are not reported because they are not executed at runtime. They\ncan use development-only dependencies, such as type stub packages, without requiring those packages\nas runtime dependencies.\n\nStandard-library imports and imports whose owning package cannot be identified unambiguously are\nalso not reported.\n\nImports of [namespace packages](https://docs.python.org/3/reference/import.html#namespace-packages)\nthemselves, such as `import ns`, are not reported: the namespace can contain modules from several\ninstallable packages. Imports of their submodules, such as `import ns.child`, are checked when the\nowning package is known. An `__init__.pyi` stub does not change this distinction.\n\nNative packages that ty can resolve only as namespace packages at runtime are also skipped. For\nother native modules, ty can use stubs to resolve the import and uv's ownership map to identify\nwhich package to declare.\n\nSome editable installations add the whole project directory to Python's import path, making both\npackage code and files such as `tests/test_app.py` importable. If uv does not identify which modules\nbelong to the installable package, ty allows dependency-group imports throughout that directory,\nincluding in package code, to avoid incorrectly flagging imports in tests and scripts.\n\n## Examples\n\nWith `requests` as a direct dependency, `urllib3` may also be installed because `requests` depends\non it:\n\n```python {data-mdtest=\"ignore\"}\nimport requests\nimport urllib3 # error: [missing-direct-dependency]\n```\n\nAdd `urllib3` to `project.dependencies` if your code imports it directly.", |
| "default": "ignore", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "missing-override-decorator": { |
| "title": "detects methods that override a superclass member without an `@override` annotation", |
| "description": "## What it does\n\nChecks for methods that override a method or attribute in a superclass but are not decorated with\n`@override`.\n\nThis rule is disabled by default. Enable it to opt in to strict `@override` enforcement for a\nproject.\n\n## Exemptions\n\nOverriding `__init__`, `__new__`, `__init_subclass__`, or `__post_init__` does not require\n`@override`, even if the method is explicitly declared by a superclass.\n\n## Why is this bad?\n\nWithout an `@override` annotation, refactors can silently change whether a method is an override.\nRequiring `@override` on every override lets ty report when an intended override stops overriding\nanything, and when a method unexpectedly starts overriding a superclass member.\n\n## Example\n\n```toml\n[environment]\npython-version = \"3.12\"\n```\n\n```python\nfrom typing import override\n\n\nclass Parent:\n def method(self) -> int:\n return 1\n\n\nclass Child(Parent):\n # when the rule is enabled\n def method(self) -> int: # error\n return 2\n\n\nclass ExplicitChild(Parent):\n @override\n def method(self) -> int: # fine\n return 2\n```", |
| "default": "ignore", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "missing-slot": { |
| "title": "detects assignments to declared attributes without instance storage", |
| "description": "## What it does\n\nChecks for assignments to declared attributes that have no matching `__slots__` entry on the class\nor its bases, and no instance dictionary to store their values.\n\n## Why is this bad?\n\nMost Python objects store their attributes in an \"instance dictionary\". Assigning to a new attribute\nadds an entry to this dictionary; deleting that attribute removes it again. Accordingly, most Python\nobjects allow for **arbitrary attributes to be set and read**. The advantage of this is that it\nallows for many dynamic features; the disadvantage is that it can be costly in terms of memory, and\ncan easily allow for typos to slip in accidentally, e.g.:\n\n```py\nclass Foo:\n def __init__(self, x):\n self.x = x\n\n def update_x(self, x):\n self.xx = x # oops, this was meant to be the same attribute set in `__init__`,\n # but ended up being an entirely separate one!\n```\n\nDefining `__slots__` lets a class reserve space for a fixed set of instance attributes instead.\nUnless an instance dictionary is inherited from a base class or requested by including `\"__dict__\"`\nin `__slots__`, instances of the class have no dictionary in which to store additional attributes.\nAttempting to assign to an attribute not declared in `__slots__` will often raise `AttributeError`\nat runtime if the instance has no instance dictionary.\n\n## Examples\n\n### Class definitions\n\n```python\nclass Item:\n __slots__ = ()\n value: int\n\n\nItem().value = 1 # error: [missing-slot]\n```\n\nIf you control the class, include the attribute in `__slots__` to make the assignment valid:\n\n```python\nclass Item:\n __slots__ = (\"value\",)\n value: int\n\n\nItem().value = 1\n```\n\n### Stub files\n\nStub files can use properties to indicate that instances have attributes that are readable and\nwritable but do not appear in `__slots__`, for example:\n\n```pyi\nclass Item:\n __slots__ = ()\n @property\n def value(self) -> int: ...\n @value.setter\n def value(self, value: int) -> None: ...\n```\n\n## References\n\n- [Python data model: `__slots__`](https://docs.python.org/3/reference/datamodel.html#slots)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "missing-type-argument": { |
| "title": "detects generic types used without explicit type parameters in type expressions", |
| "description": "## What it does\n\nChecks for generic types used without type parameters in type expressions.\n\n## Why is this bad?\n\nUsing a generic type without specifying its type parameters results in the type parameters being\nimplicitly filled with `Unknown`, reducing the precision of type checking. Explicit type parameters\nmake the intended types clear and enable the type checker to catch more errors.\n\n## Examples\n\n```python\nimport re\n\n\ndef handle(m: re.Match) -> str: # error: [missing-type-argument]\n return m.string\n\n\n# Use explicit type parameters instead:\ndef handle(m: re.Match[str]) -> str:\n return m.string\n```", |
| "default": "ignore", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "missing-typed-dict-key": { |
| "title": "detects missing required keys in `TypedDict` constructors", |
| "description": "## What it does\n\nDetects missing required keys in `TypedDict` constructor calls.\n\n## Why is this bad?\n\n`TypedDict` requires all non-optional keys to be provided during construction. Missing items can\nlead to a `KeyError` at runtime.\n\n## Example\n\n```python\nfrom typing import TypedDict\n\n\nclass Person(TypedDict):\n name: str\n age: int\n\n\n# missing required key 'age'\nalice: Person = {\"name\": \"Alice\"} # error\n\nalice[\"age\"] # KeyError\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "no-matching-overload": { |
| "title": "detects calls that do not match any overload", |
| "description": "## What it does\n\nChecks for calls to an overloaded function that do not match any of the overloads.\n\n## Why is this bad?\n\nFailing to provide the correct arguments to one of the overloads will raise a `TypeError` at\nruntime.\n\n## Examples\n\n```python\nfrom typing import overload\n\n\n@overload\ndef func(x: int): ...\n@overload\ndef func(x: bool): ...\ndef func(x: int | bool): ...\n\n\nfunc(\"string\") # error: [no-matching-overload]\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "non-callable-init-subclass": { |
| "title": "detects class definitions that will fail due to non-callable `__init_subclass__`", |
| "description": "## What it does\n\nChecks for class definitions that will fail due to non-callable `__init_subclass__` methods.\n\n## Why is this bad?\n\nIf a class defines a non-callable `__init_subclass__` method/attribute, any attempt to subclass that\nclass will raise a `TypeError` at runtime.\n\n## Examples\n\n```python\nclass Super:\n __init_subclass__ = None\n\n\nclass Sub(Super): ... # error: [non-callable-init-subclass]\n```\n\n## References\n\n- [Python data model: Customizing class creation](https://docs.python.org/3/reference/datamodel.html#customizing-class-creation)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "not-iterable": { |
| "title": "detects iteration over an object that is not iterable", |
| "description": "## What it does\n\nChecks for objects that are not iterable but are used in a context that requires them to be.\n\n## Why is this bad?\n\nIterating over an object that is not iterable will raise a `TypeError` at runtime.\n\n## Examples\n\n```python\n# TypeError: 'int' object is not iterable\nfor i in 34: # error\n pass\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "not-subscriptable": { |
| "title": "detects subscripting objects that do not support subscripting", |
| "description": "## What it does\n\nChecks for subscripting objects that do not support subscripting.\n\n## Why is this bad?\n\nSubscripting an object that does not support it will raise a `TypeError` at runtime.\n\n## Examples\n\n```python\n# TypeError: 'int' object is not subscriptable\n4[1] # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "override-of-final-method": { |
| "title": "detects overrides of final methods", |
| "description": "## What it does\n\nChecks for methods on subclasses that override superclass methods decorated with `@final`.\n\n## Why is this bad?\n\nDecorating a method with `@final` declares to the type checker that it should not be overridden on\nany subclass.\n\n## Example\n\n```python\nfrom typing import final\n\n\nclass A:\n @final\n def foo(self): ...\n\n\nclass B(A):\n def foo(self): ... # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "override-of-final-variable": { |
| "title": "detects overrides of Final class variables", |
| "description": "## What it does\n\nChecks for class variables on subclasses that override a superclass variable that has been declared\nas `Final`.\n\n## Why is this bad?\n\nDeclaring a variable as `Final` indicates to the type checker that it should not be overridden on\nany subclass.\n\n## Example\n\n```python\nfrom typing import Final\n\n\nclass A:\n X: Final[int] = 1\n\n\nclass B(A):\n X = 2 # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "parameter-already-assigned": { |
| "title": "detects multiple arguments for the same parameter", |
| "description": "## What it does\n\nChecks for calls which provide more than one argument for a single parameter.\n\n## Why is this bad?\n\nProviding multiple values for a single parameter will raise a `TypeError` at runtime.\n\n## Examples\n\n```python\ndef f(x: int) -> int:\n return x\n\n\nf(1, x=2) # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "positional-only-parameter-as-kwarg": { |
| "title": "detects positional-only parameters passed as keyword arguments", |
| "description": "## What it does\n\nChecks for keyword arguments in calls that match positional-only parameters of the callable.\n\n## Why is this bad?\n\nProviding a positional-only parameter as a keyword argument will raise `TypeError` at runtime.\n\n## Example\n\n```python\ndef f(x: int, /) -> int:\n return x\n\n\nf(x=1) # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "possibly-missing-attribute": { |
| "title": "detects references to possibly missing attributes", |
| "description": "## What it does\n\nChecks for possibly missing attributes.\n\n## Why is this bad?\n\nAttempting to access a missing attribute will raise an `AttributeError` at runtime.\n\n## Rule status\n\nThis rule is currently disabled by default because of the number of false positives it can produce.\n\n## Examples\n\n```python\nclass A:\n if __name__ == \"__main__\":\n c = 0\n\n\n# AttributeError: type object 'A' has no attribute 'c'\nA.c # error\n```", |
| "default": "ignore", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "possibly-missing-implicit-call": { |
| "title": "detects implicit calls to possibly missing methods", |
| "description": "## What it does\n\nChecks for implicit calls to possibly missing methods.\n\n## Why is this bad?\n\nExpressions such as `x[y]` and `x * y` call methods under the hood (`__getitem__` and `__mul__`\nrespectively). Calling a missing method will raise an `AttributeError` at runtime.\n\n## Examples\n\n```python\nimport datetime\n\n\nclass A:\n if datetime.date.today().weekday() != 6:\n\n def __getitem__(self, v): ...\n\n\n# TypeError: 'A' object is not subscriptable\nA()[0] # error\n```", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "possibly-missing-import": { |
| "title": "detects possibly missing imports", |
| "description": "## What it does\n\nChecks for imports of symbols that may be missing.\n\n## Why is this bad?\n\nImporting a missing module or name will raise a `ModuleNotFoundError` or `ImportError` at runtime.\n\n## Rule status\n\nThis rule is currently disabled by default because of the number of false positives it can produce.\n\n## Examples\n\n`module.py`:\n\n```python\nimport datetime\n\nif datetime.date.today().weekday() != 6:\n a = 1\n```\n\n`main.py`:\n\n```python\n# ImportError: cannot import name 'a' from 'module'\nfrom module import a # error\n```", |
| "default": "ignore", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "possibly-missing-submodule": { |
| "title": "detects accesses of submodules that may not be available as attributes on their parent module", |
| "description": "## What it does\n\nChecks for accesses of submodules that might not've been imported.\n\n## Why is this bad?\n\nWhen module `a` has a submodule `b`, `import a` isn't generally enough to let you access `a.b.` You\neither need to explicitly `import a.b`, or else you need the `__init__.py` file of `a` to include\n`from . import b`. Without one of those, `a.b` is an `AttributeError`.\n\n## Examples\n\n```python\nimport html\n\n# AttributeError: module 'html' has no attribute 'parser'\nhtml.parser # error\n```", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "possibly-unresolved-reference": { |
| "title": "detects references to possibly undefined names", |
| "description": "## What it does\n\nChecks for references to names that are possibly not defined.\n\n## Why is this bad?\n\nUsing an undefined variable will raise a `NameError` at runtime.\n\n## Rule status\n\nThis rule is currently disabled by default because of the number of false positives it can produce.\n\n## Example\n\n```python\nfor i in range(int(input())):\n x = i\n\n# NameError: name 'x' is not defined\nprint(x) # error\n```", |
| "default": "ignore", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "pydantic-discarded-extra-argument": { |
| "title": "detects extra constructor arguments that Pydantic silently discards", |
| "description": "## What it does\n\nChecks for extra keyword arguments that Pydantic silently discards when a model uses\n`extra=\"ignore\"`, either implicitly or explicitly.\n\n## Why is this bad?\n\nA discarded argument has no effect on the constructed model, but it may indicate a misspelled field\nname or an incorrect assumption about the model's schema.\n\n## Example\n\n```python {data-mdtest=\"ignore\"}\nfrom pydantic import BaseModel\n\n\nclass User(BaseModel):\n name: str\n admin: bool = False\n\n\nuser = User(name=\"Alice\", admni=True) # error: [pydantic-discarded-extra-argument]\n```\n\nIf the field name has been misspelled, fix the typo. Otherwise, consider removing the extra\nargument, or explicitly configure the model with `extra=\"allow\"`.", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "raw-string-type-annotation": { |
| "title": "detects raw strings in type annotation positions", |
| "description": "## What it does\n\nChecks for raw-strings in type annotation positions.\n\n## Why is this bad?\n\nStatic analysis tools like ty can't analyze type annotations that use raw-string notation.\n\n## Examples\n\n```python\ndef test() -> r\"int\": # error\n return 1\n```\n\nUse instead:\n\n```python\ndef test() -> \"int\":\n return 1\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "redundant-cast": { |
| "title": "detects redundant `cast` calls", |
| "description": "## What it does\n\nDetects redundant `cast` calls where the value already has the target type.\n\n## Why is this bad?\n\nThese casts have no effect and can be removed.\n\n## Example\n\n```python\nfrom typing import cast\n\n\ndef f() -> int:\n return 10\n\n\n# Redundant\ncast(int, f()) # error\n```", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "redundant-condition": { |
| "title": "detects conditions that are always truthy or always falsey", |
| "description": "## What it does\n\nDetects boolean conditions where the condition can be statically inferred to be always true or\nalways false due to the inferred type of the condition.\n\nThis rule is enabled by default, and is deliberately not comprehensive. In order to avoid false\npositives, it excludes conditions that meet any of these criteria:\n\n- The boolean test is inferred as evaluating to `True` itself, `False` itself, or an exact integer\n such as `1` or `0`.\n- The boolean test can be inferred as always evaluating to `True` and `False`, but this inference is\n due to boolean-test short-circuiting in `if` conditions, `while` conditions or `assert` tests\n rather than the inferred type of the boolean test.\n- The condition uses a walrus operator (`:=`). The assignment's side effect may be intentional, even\n when its result has fixed truthiness.\n\n## Why is this bad?\n\nA boolean condition that is always true or always false usually indicates a mistake in your code,\nand can often lead to incorrect behavior. If an `if` condition is inferred as always false,\nmoreover, ty will infer all code within that `if` branch as being unreachable, and will not report\nany diagnostics on code in that region.\n\n## Examples\n\nA common error that triggers this rule is to forget to call a function, for example:\n\n```py\nimport random\n\n\ndef should_do_action() -> bool:\n return random.choice([True, False])\n\n\n# oops! You forgot the parentheses here... this should have been `if should_do_action()`.\n# Because it's not, this will always be `True`:\nif should_do_action: # error: [redundant-condition]\n print(\"Doing stuff...\")\n```\n\nAnother common mistake is to forget to `await` a coroutine:\n\n```py\nimport random\n\n\nasync def should_do_async_action():\n return random.choice([True, False])\n\n\nasync def main():\n # oops! Forgot the await here... this should have been `if await should_do_async_action()`.\n # Because it's not, this will always be `True`:\n if should_do_async_action(): # error: [redundant-condition]\n print(\"Doing stuff async...\")\n```\n\nOr to forget that `tuple[X]` means \"A tuple with exactly one element\" rather than \"a tuple with an\narbitrary number of elements\" (for which you'd use `tuple[X, ...]`):\n\n```py\n# you almost certainly meant to write `tuple[str, ...]` here rather than `tuple[str]`...\ndef consume_tuples(x: tuple[str]):\n # ...and that means that this later condition is inferred as always being True by ty:\n if x: # error: [redundant-condition]\n print(\"Got a non-empty tuple\")\n```\n\nSome Pythonistas fall into the trap of thinking that a generator expression will be falsy if it has\nzero elements inside it -- but generator expressions are lazy, and so they're always truthy unless\nyou collect them into a tuple:\n\n```py\ndef test_my_data(data: list[int]):\n # this will always be `True`, because the asserted object is a `types.GeneratorType` instance,\n # not a `tuple`! `assert any(item for item in data if item > 42)`\n # is probably what you meant instead.\n assert (item for item in data if item > 42) # error: [redundant-condition]\n```\n\n## Boolean operators used to compute values\n\nThe rule checks `and` and `or` operands when the expression is used as a condition: in an `if`,\n`elif`, `while`, or `assert` test, a conditional expression, a comprehension filter, a match guard,\nor as the operand of `not`. It does not flag `and` or `or` expressions used to compute values --\neven if an operand in an `and` or `or` expression is always truthy, it doesn't necessarily make the\nexpression redundant:\n\n```py\ndef f(): ...\ndef g(): ...\n\n\ndef test(coinflip: bool):\n # could also be written as `func = f if coinflip else g`,\n # but use of an `and` expression for this is common in older codebases.\n func = coinflip and f or g\n\n # `func` will be the `f` function if `coinflip` is `True`,\n # and the `g` function otherwise\n func()\n```\n\nThis also allows calls that are deliberately always falsy but are used for their side effects:\n\n```py\nfrom unittest.mock import patch\n\n\ndef ask_to_continue() -> bool:\n return input(\"Continue? \") == \"yes\"\n\n\ndef test_ask_to_continue():\n prompts = []\n with patch(\n \"builtins.input\",\n side_effect=lambda prompt: prompts.append(prompt) or \"yes\",\n ):\n assert ask_to_continue()\n\n assert prompts == [\"Continue? \"]\n```\n\nBy contrast, `not` always produces a boolean, so we will still emit a diagnostic on the following\nexample -- negating the truthiness of a function object is pointless, since a function object is\nalways truthy:\n\n```py\ndef f(): ...\n\n\nvalue = not f # error: [redundant-condition]\n```\n\n## Known issues and workarounds\n\nThis rule can sometimes trigger on code that is not incorrect, but could be written in a clearer\nway. For example, the rule will flag this code:\n\n```py\ndef find_duplicate_coordinates(coordinates: list[tuple[int, int]]):\n seen: set[tuple[int, int]] = set()\n # error: [redundant-condition] \"Expression `seen.add(coord)` is always falsy (has type `None`)\"\n duplicates = {coord for coord in coordinates if coord in seen or seen.add(coord)}\n print(f\"Duplicates are {duplicates}\")\n```\n\nThe error here is triggered due to `seen.add(coord)` being used in a boolean expression, despite the\nfact that `set.add()` always returns `None`. Here this is deliberate: `set.add()` is being used for\nits side effect.\n\nTo workaround this issue, the above code could be rewritten like this, which may also be easier for\nsome readers to understand:\n\n```py\ndef find_duplicate_coordinates(coordinates: list[tuple[int, int]]):\n seen: set[tuple[int, int]] = set()\n duplicates: set[tuple[int, int]] = set()\n\n for coord in coordinates:\n if coord in seen:\n duplicates.add(coord)\n else:\n seen.add(coord)\n\n print(f\"Duplicates are {duplicates}\")\n```", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "redundant-condition-strict": { |
| "title": "detects conditions that are always truthy or always falsey (strict)", |
| "description": "## What it does\n\nDetects boolean conditions where the condition can be statically inferred to be always true or\nalways false.\n\nThis rule is disabled by default. It exclusively covers cases that its sibling (enabled-by-default)\nrule `redundant-condition` does not cover. These cases often flag real bugs in user code, but also\nhave a significantly higher rate of unavoidable false positives than other cases.\n\nThis rule reports redundant conditions that meet any of these criteria:\n\n- The boolean test is inferred as evaluating to `True` itself, `False` itself, or an exact integer\n such as `1` or `0`.\n- Short-circuit evaluation means the condition can be guaranteed to be always truthy or always falsy\n despite fixed truthiness not being guaranteed by the inferred type of the expression's value\n (see \"Short-circuiting boolean conditions\" below for an example).\n- The condition uses a walrus operator (`:=`). The assignment's side effect may be intentional, even\n when its result has fixed truthiness.\n\n## Why is this bad?\n\nA boolean condition that is always true or always false usually indicates a mistake in your code,\nand can often lead to incorrect behavior. If an `if` condition is inferred as always false,\nmoreover, ty will infer all code within that `if` branch as being unreachable, and will not report\nany diagnostics on code in that region.\n\n## Examples\n\nA common error in Python code is to make the mistake of thinking that indexing into a `bytes` object\nwill get you an object of type `bytes`. But `bytes` work differently to `str`s in Python -- although\na string is a sequence of strings, a bytestring is a sequence of `int`s, so indexing into a `bytes`\nobject gives you an `int`. This rule can catch that error by alerting you to the fact that checking\nwhether a `bytes` object is unequal to an `int` will always evaluate to `True`:\n\n```py\ndef validate_record(data: bytes) -> None:\n if data[0] != b\"\\x1e\": # error: [redundant-condition-strict]\n raise ValueError(\"Invalid record separator\")\n```\n\nAnother common mistake is to assume that annotating `**kwargs` with `dict[str, str]` describes the\ndictionary containing the keyword arguments. In fact, a `**kwargs` annotation describes each\nindividual keyword argument, so this annotation says that every value is itself a dictionary.\nComparing one of those values with a string will therefore always evaluate to `False`:\n\n```py\ndef trace(**kwargs: dict[str, str]) -> None:\n if kwargs.get(\"operation\") == \"task\": # error: [redundant-condition-strict]\n print(\"Tracing task\")\n```\n\n## Short-circuiting boolean conditions\n\nIn some situations, ty can know that a condition will always be true, or it can know that a\ncondition will always be false, even when this is not guaranteed by the inferred type of that\ncondition. This is because of the way that Python short-circuits evaluation of conditions in the\ncontext of `if` tests, `while` tests and `assert` statements.\n\nConsider a class whose comparison method has an `object` return type:\n\n```py\nfrom typing_extensions import reveal_type\n\n\nclass Comparable:\n def __lt__(self, other: int) -> object: ...\n\n\ndef check(value: Comparable):\n reveal_type(value < 1 < 0) # revealed: ~AlwaysTruthy\n\n if value < 1 < 0: # error: [redundant-condition-strict] \"always false\"\n pass\n```\n\nOutside the context of an `if` test, the revealed type of the condition here is `~AlwaysTruthy`: in\nother words, ty knows that this expression is not *always true*, but cannot guarantee that it is\ndefinitely *always false*. It could be an object that is sometimes true and sometimes false -- for\nexample, a `list` (which is falsy when it is empty, and truthy otherwise).\n\nNonetheless, when `value < 1 < 0` is used directly as a condition, ty knows that the condition will\nalways be falsy and the `if` branch will never be taken. Python tests the truthiness of the object\nreturned by `Comparable.__lt__` once: if it is falsy, the condition fails immediately. If it is\ntruthy, Python evaluates `1 < 0`, which is false. There is no second truthiness test of the object\nreturned by `__lt__`.\n\nIf the chained comparison is saved as a variable first, its value can be the object returned by\n`__lt__`, if that object was falsy when first tested. The `if result` statement then tests that\nobject's truthiness again. A user-defined `__bool__` method can return a different result on that\nsecond call, so ty cannot guarantee that the saved value is still falsy, and no diagnostic is\nemitted:\n\n```py\ndef check_saved(value: Comparable):\n result = value < 1 < 0\n if result: # no diagnostic\n pass\n```\n\n## Exemptions\n\nLike `redundant-condition`, this rule checks subexpressions of an `and` or `or` expression only when\nthe outer expression is used as a condition. This is to avoid emitting false-positive diagnostics on\ncode like the following, where the `and` expression is clearly not redundant despite the fact that\nboth `CONSTANT_1` and `CONSTANT_2` are always truthy:\n\n```py\nfrom typing import Final\n\n\nCONSTANT_1: Final = 1\nCONSTANT_2: Final = 2\n\n\ndef do_something(coinflip: bool):\n # could also be written as `constant_to_use = CONSTANT_1 if coinflip else CONSTANT_2`,\n # but use of an `and` expression for this is common in older codebases.\n constant_to_use = coinflip and CONSTANT_1 or CONSTANT_2\n\n # do something with `constant_to_use` now...\n ...\n```\n\nUnlike `and` and `or`, however, `not` explicitly converts its operand to a boolean, so the rule\nchecks `not` expressions in every context.\n\nAnother exemption applied by this rule concerns `assert`-statement tests. A common pattern in Python\ncode is to use defensive `assert`s to enforce behaviour at runtime, even when the asserted condition\ncan be inferred statically to be always true. For example:\n\n```py\ndef add_one(x: int) -> int:\n assert isinstance(x, int) # no diagnostic\n return x + 1\n```\n\nThis kind of defensive behaviour is often reasonable, since the author of a library cannot guarantee\nthat end users of the library will run a type checker on code calling into the library, meaning that\nit's entirely possible at runtime for an object passed into the `x`a parameter above to be a `str`\n(for example) even though the parameter annotation states that only `int`s can ever be passed in.\nThis rule therefore also exempts all assertion tests or subexpressions that evaluate to a subtype of\n`int` or `bool`:\n\n`redundant-condition-strict` can still trigger on `assert` statements in some contexts, however. For\nexample, `redundant-condition-strict` will be emitted on the below example, where the left-hand side\nof the `and` expression is always true and not a subtype of `bool` or `int`, but where the condition\nis nonetheless excluded from the enabled-by-default `redundant-condition` rule due to the use of the\nwalrus operator:\n\n```py\ndef func() -> bool:\n return True\n\n\ndef test_func():\n assert (result := func) and result != func() # error: [redundant-condition-strict]\n```\n\nFor similar reasons to the `assert` exemptions, this rule also exempts always-false `if` or `elif`\nconditions when their bodies end in a defensive check: a `raise`, an assertion that could fail, a\ncall returning `Never`, an `await` to a call returning `Never`, or `return NotImplemented`:\n\n```py\nimport sys\n\n\ndef add_two(x: int) -> int:\n if not isinstance(x, int): # no diagnostic\n raise TypeError(\"need an int!!\")\n return x + 2\n\n\ndef add_three(x: int) -> int:\n if not isinstance(x, int): # no diagnostic\n assert False, \"unreachable\"\n return x + 3\n\n\ndef add_four(x: int) -> int:\n if not isinstance(x, int): # no diagnostic\n sys.exit(1)\n return x + 4\n\n\nclass Foo:\n def __init__(self, data: int):\n self.data = data\n\n def __add__(self, other: \"Foo\"):\n if not isinstance(other, Foo): # no diagnostic\n return NotImplemented\n return Foo(self.data + other.data)\n```\n\nAnd an exemption is applied for always-true `if` or `elif` statements that are followed by branches\nwhich contain defensive checks:\n\n```py\nfrom typing_extensions import assert_never\n\n\ndef parse_data(data: int | str):\n if isinstance(data, int):\n print(\"got an int\")\n elif isinstance(data, str): # Always true, but no diagnostic, since\n # the `else` branch following this branch is always terminal.\n # (`assert_never` returns `Never`, indicating that it always raises an exception)\n print(\"got a str\")\n else:\n assert_never(data)\n\n\ndef parse_data_early_return(data: int | str):\n if isinstance(data, int):\n print(\"got an int\")\n return\n\n # Always true, but no diagnostic, since\n # the suite following this branch is always terminal\n # (every control-flow path following this `if` statement ends in a `raise` statement)\n if isinstance(data, str):\n print(\"got a str\")\n return\n\n raise AssertionError(\"unexpected data\")\n```\n\nAny conditions defined in relation to `sys.version_info`, `sys.platform`, `os.name` or\n`typing.TYPE_CHECKING` are also exempted. The rule recursively follows the definitions of names and\nattributes across module boundaries to determine if a name or attribute was indirectly defined in\nrelation to one of these highly special-cased symbols:\n\n```toml\n[environment]\npython-version = \"3.14\"\npython-platform = \"linux\"\n```\n\n```py\nimport os\nimport sys\nfrom typing import TYPE_CHECKING\n\nif sys.version_info >= (3, 14): # inferred as always true here, but no diagnostic\n pass\n\nif sys.platform == \"win32\": # inferred as always false here, but no diagnostic\n pass\n\nLINE_ENDING = \"\\n\" if os.name == \"posix\" else \"\\r\\n\"\n\nif LINE_ENDING == \"\\n\": # inferred as always true here, but no diagnostic\n pass\n\nif TYPE_CHECKING: # inferred as always true, but no diagnostic\n pass\n```\n\nConditions involving these constants, or conditions involving values defined in relation to these\nconstants, can often be inferred as always-true or always-false by ty. Indeed, these conditions\nusually *will* be always true or always false across a single invocation run of a Python programme.\nNonetheless, Python code is often written so that it can work on multiple different Python versions\nand/or multiple different operating systems, and a condition that is always true on one operating\nsystem might very well be always false on another operating system (for example). Flagging these\nconditions as being always true or always false would only add noise: the aim of the rule is to flag\nconditions that are *unintentionally* always true or always false.\n\nLastly, some conditions involving literal integers and booleans in the AST are also exempted:\nthere's no reason why you'd use a condition like this unless it was intentional.\n\n```py\nif True: # inferred as always true (obviously), but no diagnostic\n pass\n\nif 0:\n pass # inferred as always false, but no diagnostic\n```\n\n## Known issues and workarounds\n\nThis rule can often trigger on code that is not incorrect, but could be written in a clearer way.\nFor example, the rule will flag this code:\n\n```py\nfrom enum import Enum\n\n\nclass YesOrNo(Enum):\n YES = 1\n NO = 0\n\n\ndef say_yes_or_no(what_to_say: YesOrNo):\n if what_to_say == YesOrNo.YES:\n print(\"yes\")\n elif what_to_say == YesOrNo.NO: # error: [redundant-condition-strict]\n print(\"no\")\n```\n\nThis snippet could be written more clearly as this, which would not trigger the rule owing to the\nexemptions described in the section above:\n\n```py\ndef say_yes_or_no(what_to_say: YesOrNo):\n if what_to_say == YesOrNo.YES:\n print(\"yes\")\n else:\n assert what_to_say == YesOrNo.NO\n print(\"no\")\n```\n\nor the snippet could also be rewritten as this, which would also be fine according to the rule's\nheuristics:\n\n```py\nfrom typing_extensions import assert_never\n\n\ndef say_yes_or_no(what_to_say: YesOrNo):\n if what_to_say == YesOrNo.YES:\n print(\"yes\")\n elif what_to_say == YesOrNo.NO:\n print(\"no\")\n else:\n assert_never(what_to_say)\n```\n\nIn a similar vein, this rule can often flag `and` or `or` expressions that have operands which are\ndeliberately always truthy or deliberately always falsy, because the purpose of the operand is to\nhave some side effect occur. For example:\n\n```py\nimport random\nfrom typing import Literal\n\n\ndef want_to_go_fishing() -> bool:\n return random.choice([True, False])\n\n\ndef weather_report() -> Literal[\"rainy\", \"sunny\", \"cloudy\"]:\n return random.choice([\"rainy\", \"sunny\", \"cloudy\"])\n\n\ndef have_fishing_supplies() -> bool:\n return random.choice([True, False])\n\n\ndef main():\n if (\n want_to_go_fishing()\n and (weather := weather_report()) # error: [redundant-condition-strict]\n and have_fishing_supplies()\n ):\n print(f\"The weather is {weather}, let's go fishing\")\n```\n\nThe middle operand in the above `and` expression is always truthy. This might be deliberate, but\neven if it is, the function would arguably be clearer if it were written like this instead:\n\n```py\ndef main():\n if want_to_go_fishing():\n weather = weather_report()\n if have_fishing_supplies():\n print(f\"The weather is {weather}, let's go fishing\")\n```\n\nLastly, the rule cannot reliably distinguish in all cases comparisons that are intentionally always\ntrue/false from those that are unintentionally always true/false. The rule takes care to avoid\nflagging code that uses `if TYPE_CHECKING`, `if sys.version_info < (X, Y)`, `if sys.platform == ...`\nand `if os.name == ...`. But it cannot reliably determine that code like this was written the way it\nwas meant to be:\n\n```py\nDEBUGGING = 0\n\nif DEBUGGING: # error: [redundant-condition-strict]\n print(\"Doing debugging stuff...\")\n```", |
| "default": "ignore", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "redundant-final-classvar": { |
| "title": "detects redundant combinations of `ClassVar` and `Final`", |
| "description": "## What it does\n\nChecks for redundant combinations of the `ClassVar` and `Final` type qualifiers.\n\n## Why is this bad?\n\nAn attribute that is marked `Final` in a class body is implicitly a class variable. Marking it as\n`ClassVar` is therefore redundant.\n\nNote that this diagnostic is not emitted for dataclass fields or protocol members, where\n`ClassVar[Final[int]]` has a distinct meaning from `Final[int]`.\n\n## Examples\n\n```python\nfrom typing import ClassVar, Final\n\n\nclass C:\n # redundant\n x: ClassVar[Final[int]] = 1 # error\n # redundant\n y: Final[ClassVar[int]] = 1 # error\n```", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "shadowed-type-variable": { |
| "title": "detects type variables that shadow type variables from outer scopes", |
| "description": "## What it does\n\nChecks for type variables in nested generic classes or functions that shadow type variables from an\nenclosing scope.\n\n## Why is this bad?\n\nShadowing type variables makes the code confusing and is disallowed by the typing spec.\n\n## Examples\n\n```toml\n[environment]\npython-version = \"3.12\"\n```\n\n```python\nclass Outer[T]:\n # `T` is already used by `Outer`\n class Inner[T]: ... # error\n\n # `T` is already used by `Outer`\n def method[T](self, x: T) -> T: # error\n return x\n```\n\n## References\n\n- [Typing spec: Generics](https://typing.python.org/en/latest/spec/generics.html#introduction)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "static-assert-error": { |
| "title": "Failed static assertion", |
| "description": "## What it does\n\nMakes sure that the argument of `static_assert` is statically known to be true.\n\n## Why is this bad?\n\nA `static_assert` call represents an explicit request from the user for the type checker to emit an\nerror if the argument cannot be verified to evaluate to `True` in a boolean context.\n\n## Examples\n\n```python\nfrom ty_extensions import static_assert\n\n# evaluates to `False`\nstatic_assert(1 + 1 == 3) # error\n\n# does not have a statically known truthiness\nstatic_assert(int(2.0 * 3.0) == 6) # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "subclass-of-dataclass-with-order": { |
| "title": "detects subclasses of dataclasses with `order=True`", |
| "description": "## What it does\n\nChecks for classes that inherit from a dataclass with `order=True`.\n\n## Why is this bad?\n\nWhen a dataclass has `order=True`, comparison methods (`__lt__`, `__le__`, `__gt__`, `__ge__`) are\ngenerated that compare instances as tuples of their fields. These methods raise a `TypeError` at\nruntime when comparing instances of different classes in the inheritance hierarchy, even if one is a\nsubclass of the other.\n\nThis violates the [Liskov Substitution Principle][liskov-substitution-principle] because child class\ninstances cannot be used in all contexts where parent class instances are expected.\n\n## Example\n\n```python\nfrom dataclasses import dataclass\n\n\n@dataclass(order=True)\nclass Parent:\n value: int\n\n\nclass Child(Parent): # error\n pass\n\n\n# At runtime, this raises TypeError:\n# Child(1) < Parent(2)\n```\n\nConsider using [`functools.total_ordering`][total_ordering] instead, which does not have this\nlimitation.\n\n[liskov-substitution-principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle\n[total_ordering]: https://docs.python.org/3/library/functools.html#functools.total_ordering", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "subclass-of-final-class": { |
| "title": "detects subclasses of final classes", |
| "description": "## What it does\n\nChecks for classes that subclass final classes.\n\n## Why is this bad?\n\nDecorating a class with `@final` declares to the type checker that it should not be subclassed.\n\n## Example\n\n```python\nfrom typing import final\n\n\n@final\nclass A: ...\n\n\nclass B(A): ... # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "super-call-in-named-tuple-method": { |
| "title": "detects `super()` calls in methods of `NamedTuple` classes", |
| "description": "## What it does\n\nChecks for calls to `super()` inside methods of `NamedTuple` classes.\n\n## Why is this bad?\n\nUsing `super()` in a method of a `NamedTuple` class will raise an exception at runtime.\n\n## Examples\n\n```python\nfrom typing import NamedTuple\n\n\nclass F(NamedTuple):\n x: int\n\n def method(self):\n # super() is not supported in methods of NamedTuple classes\n super() # error\n```\n\n## References\n\n- [Python documentation: super()](https://docs.python.org/3/library/functions.html#super)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "too-many-positional-arguments": { |
| "title": "detects calls passing too many positional arguments", |
| "description": "## What it does\n\nChecks for calls that pass more positional arguments than the callable can accept.\n\n## Why is this bad?\n\nPassing too many positional arguments will raise `TypeError` at runtime.\n\n## Example\n\n```python\ndef f(): ...\n\n\nf(\"foo\") # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "type-assertion-failure": { |
| "title": "detects failed type assertions", |
| "description": "## What it does\n\nChecks for `assert_type()` and `assert_never()` calls where the actual type is not the same as the\nasserted type.\n\n## Why is this bad?\n\n`assert_type()` allows confirming the inferred type of a certain value.\n\n## Example\n\n```toml\n[environment]\npython-version = \"3.11\"\n```\n\n```python\nfrom typing import assert_type\n\n\ndef _(x: int):\n assert_type(x, int) # fine\n # Actual type does not match asserted type\n assert_type(x, str) # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unavailable-implicit-super-arguments": { |
| "title": "detects invalid `super()` calls where implicit arguments are unavailable.", |
| "description": "## What it does\n\nDetects invalid `super()` calls where implicit arguments like the enclosing class or first method\nargument are unavailable.\n\n## Why is this bad?\n\nWhen `super()` is used without arguments, Python tries to find two things: the nearest enclosing\nclass and the first argument of the immediately enclosing function (typically self or cls). If\neither of these is missing, the call will fail at runtime with a `RuntimeError`.\n\n## Examples\n\n```python\n# no enclosing class or function found\nsuper() # error\n\n\ndef func():\n # no enclosing class or first argument exists\n super() # error\n\n\nclass A:\n # no enclosing function to provide the first argument\n f = super() # error\n\n def method(self):\n def nested():\n # first argument does not exist in this nested function\n super() # error\n\n # first argument does not exist in this lambda\n lambda: super() # error\n\n # argument is not available in generator expression\n (super() for _ in range(10)) # error\n\n super() # okay! both enclosing class and first argument are available\n```\n\n## References\n\n- [Python documentation: super()](https://docs.python.org/3/library/functions.html#super)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unbound-type-variable": { |
| "title": "detects type variables used outside of their bound scope", |
| "description": "## What it does\n\nChecks for type variables that are used in a scope where they are not bound to any enclosing generic\ncontext.\n\n## Why is this bad?\n\nUsing a type variable outside of a scope that binds it has no well-defined meaning.\n\n## Examples\n\n```python\nfrom typing import TypeVar, Generic\n\nT = TypeVar(\"T\")\nS = TypeVar(\"S\")\n\n# unbound type variable in module scope\nx: T # error\n\n\nclass C(Generic[T]):\n # S is not in this class's generic context\n x: list[S] = [] # error\n```\n\n## References\n\n- [Typing spec: Scoping rules for type variables](https://typing.python.org/en/latest/spec/generics.html#scoping-rules-for-type-variables)", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "undefined-reveal": { |
| "title": "detects usages of `reveal_type` without importing it", |
| "description": "## What it does\n\nChecks for calls to `reveal_type` without importing it.\n\n## Why is this bad?\n\nUsing `reveal_type` without importing it will raise a `NameError` at runtime.\n\n## Examples\n\n```python\n# NameError: name 'reveal_type' is not defined\n# error\nreveal_type(1) # revealed: Literal[1]\n```", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unknown-argument": { |
| "title": "detects unknown keyword arguments in calls", |
| "description": "## What it does\n\nChecks for keyword arguments in calls that don't match any parameter of the callable.\n\n## Why is this bad?\n\nProviding an unknown keyword argument will raise `TypeError` at runtime.\n\n## Example\n\n```python\ndef f(x: int) -> int:\n return x\n\n\nf(x=1, y=2) # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unresolved-attribute": { |
| "title": "detects references to unresolved attributes", |
| "description": "## What it does\n\nChecks for unresolved attributes.\n\n## Why is this bad?\n\nAccessing an unbound attribute will raise an `AttributeError` at runtime. An unresolved attribute is\nnot guaranteed to exist from the type alone, so this could also indicate that the object is not of\nthe type that the user expects.\n\n## Examples\n\n```python\nclass A: ...\n\n\n# AttributeError: 'A' object has no attribute 'foo'\nA().foo # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unresolved-global": { |
| "title": "detects `global` statements with no definition in the global scope", |
| "description": "## What it does\n\nDetects variables declared as `global` in an inner scope that have no explicit bindings or\ndeclarations in the global scope.\n\n## Why is this bad?\n\nFunction bodies with `global` statements can run in any order (or not at all), which makes it hard\nfor static analysis tools to infer the types of globals without explicit definitions or\ndeclarations.\n\n## Example\n\n### Assigning without a global-scope declaration\n\n```python\ndef f():\n # unresolved global\n global x # error\n x = 42\n\n\ndef g():\n print(x) # unresolved reference\n```\n\n### Use instead\n\n#### Declare the global\n\n```python\nx: int\n\n\ndef f():\n global x\n x = 42\n\n\ndef g():\n print(x)\n```\n\n#### Initialize the global\n\n```python\nx: int | None = None\n\n\ndef f():\n global x\n x = 42\n\n\ndef g():\n print(x)\n```", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unresolved-import": { |
| "title": "detects unresolved imports", |
| "description": "## What it does\n\nChecks for import statements for which the module cannot be resolved.\n\n## Why is this bad?\n\nImporting a module that cannot be resolved will raise a `ModuleNotFoundError` at runtime.\n\n## Examples\n\n```python\n# ModuleNotFoundError: No module named 'foo'\nimport foo # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unresolved-reference": { |
| "title": "detects references to names that are not defined", |
| "description": "## What it does\n\nChecks for references to names that are not defined.\n\n## Why is this bad?\n\nUsing an undefined variable will raise a `NameError` at runtime.\n\n## Example\n\n```python\n# NameError: name 'x' is not defined\nprint(x) # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unsound-assignment": { |
| "title": "detects assignments that unsoundly assign a type that is not a subtype of the declared type", |
| "description": "## What it does\n\nDetects variable assignments that unsoundly assign a type that is not a [subtype] of a variable's\ndeclared type.\n\nThis rule is a stricter version of `invalid-assignment`. Whereas that rule also flags assignments to\nattributes and subscripts, however, this rule is only applied to variable assignments.\n\nThis rule has no effect on stub files.\n\n## Why is this bad?\n\nBy default, type checkers consider an assignment valid if the inferred type of the assigned value is\n[assignable] to the target's declared type. However, this makes it easy for incorrect types to\npercolate through your code unexpectedly due to a single expression being inferred as `Any`. This\ncan easily lead to runtime errors that are not caught by the type checker:\n\n```py\nfrom typing import Any\n\n\ndef returns_any() -> Any:\n return \"not an integer\"\n\n\n# error: \"Unsound assignment: `Any` is not a subtype of `int`\"\nmy_integer: int = returns_any()\n\n# Fails at runtime, even though the type checker infers both operands as being of type `int`!\nmy_integer + 42\n```\n\nThis rule treats [\"fully static\"][fully-static] declared types as \"typed boundaries\" for your code.\nWith this rule enabled, ty would emit an error on the `my_integer: int = returns_any()` assignment,\nsince the `returns_any()` call is inferred as having type `Any`, and `Any` is not a subtype of\n`int`. This helps prevent the unsoundness from spreading far from its original source (in this case,\nthe return type of the `returns_any` function).\n\nNote that this rule is only applied to assignments where the declared type is\n[fully static][fully-static]. It will not trigger if `Any` or `Unknown` appear anywhere in the\ndeclared type, either implicitly or explicitly:\n\n```py\nfrom typing import Any\n\n\ndef returns_any() -> Any:\n return \"not an integer\"\n\n\nexplicitly_dynamic: Any = returns_any() # no error\nalso_dynamic: list[Any] = returns_any() # no error\n\n# no `unsound-assignment` error, since `list` is implicitly the same as `list[Unknown]`\n# (which is what the `missing-type-argument` error is complaining about)\n#\n# error: [missing-type-argument]\nimplicitly_dynamic: list = returns_any()\n```\n\nThis rule works especially well when combined with ty's `missing-type-argument` rule.\n\n## Examples\n\n```py\nfrom typing import Any\n\n\ndef returns_any() -> Any:\n return 42\n\n\n# error: \"Unsound assignment: `Any` is not a subtype of `int`\"\nmy_integer: int = returns_any()\n\nanother_integer: int\n\n# error: \"Unsound assignment: `Any` is not a subtype of `int`\"\nanother_integer = returns_any()\n```\n\nNarrow the value before assigning it to fix the diagnostics:\n\n```py\nfrom typing import Any\n\n\ndef returns_any() -> Any:\n return 42\n\n\nvalue = returns_any()\nassert isinstance(value, int)\nmy_integer: int = value # no error: `Any & int` is a subtype of `int`\n```\n\n## Default level\n\nThis rule is disabled by default. It is intended for advanced users wanting additional soundness\nchecks from their type checker, not for users who have just started to use type checkers on their\nPython code.\n\n## See also\n\n- `unsound-return-statement` is a similar rule that triggers on unsound `return` statements rather\n than unsound assignments\n- `unsound-yield` is a similar rule that triggers on unsound `yield` expressions rather than unsound\n assignments\n\n[assignable]: https://typing.python.org/en/latest/spec/glossary.html#term-assignable\n[fully-static]: https://typing.python.org/en/latest/spec/glossary.html#term-fully-static-type\n[subtype]: https://typing.python.org/en/latest/spec/glossary.html#term-subtype", |
| "default": "ignore", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unsound-return-statement": { |
| "title": "detects return statements that unsoundly return a type that is not a subtype of the function's annotated return type", |
| "description": "## What it does\n\nDetects `return` statements that unsoundly return a type that is not a [subtype] of the function's\nannotated return type.\n\nThis lint is a stricter version of `invalid-return-type`.\n\n## Why is this bad?\n\nBy default, type checkers consider a `return` statement valid if the inferred type of the object\nbeing returned is [assignable] to the annotated return type of the function it's in. However, this\nmakes it easy for incorrect types to percolate through your code unexpectedly due to a single\nexpression being inferred as `Any`. This can easily lead to runtime errors that are not caught by\nthe type checker:\n\n```py\nfrom typing import Any\n\n\ndef returns_any() -> Any:\n return \"foo\"\n\n\ndef returns_int() -> int:\n # error: \"Unsound return statement: `Any` is not a subtype of `int`\"\n return returns_any()\n\n\n# fails at runtime, even though the type checker infers both operands as being of type `int`!\nreturns_int() + 42\n```\n\nThis rule allows you to use [\"fully static\"][fully-static] return types as \"typed boundaries\" for\nyour code. With this rule enabled, ty would emit an error on the `return returns_any()` statement in\n`returns_int`, since the `returns_any()` call is inferred as having type `Any`, and `Any` is not a\nsubtype of `int`. This helps prevent the unsoundness from spreading far from its original source (in\nthis case, the return type of the `returns_any` function).\n\nNote that this rule is only applied to functions annotated as returning [fully static][fully-static]\ntypes. It will not trigger if `Any` or `Unknown` appear anywhere in your return type, either\nimplicitly or explicitly:\n\n```py\nfrom typing import Any\n\n\ndef returns_any() -> Any:\n return \"foo\"\n\n\n# error: [missing-type-argument]\ndef returns_unparameterized_tuple() -> tuple:\n # no error, since the return type is implicitly `tuple[Unknown, ...]`\n # (which is what the `missing-type-argument` error is complaining about on the line above!)\n return returns_any()\n\n\ndef returns_list_of_any() -> list[Any]:\n # no error, since the return type is explicitly `list[Any]`\n return returns_any()\n```\n\nThis rule works especially well when combined with ty's `missing-type-argument` and\n`unsound-assignment` rules, as well as the Ruff rules [`ANN201`][ann201], [`ANN202`][ann202],\n[`ANN204`][ann204], [`ANN205`][ann205], and [`ANN206`][ann206]. Enabling all these rules at once\neffectively makes it much less likely that a `return` statement can lead to unsoundness \"leaking\"\nout of a function unless that function has been *explicitly* annotated with a dynamic type in some\nway (`-> Any` or `-> tuple[Any]`, for example).\n\nThis rule is analogous to mypy's [`no-any-return`][no-any-return] error code, which is enabled by\nmypy’s [`--strict`][mypy-strict] mode and can also be enabled on its own using mypy’s\n[`--warn-return-any`][warn-return-any] option.\n\n## Examples\n\n```py\nfrom typing import Any\n\n\ndef returns_any() -> Any:\n return 42\n\n\ndef returns_int() -> int:\n # error: \"Unsound return statement: `Any` is not a subtype of `int`\"\n return returns_any()\n```\n\nNarrow the type to a subtype of `int` to fix the diagnostic:\n\n```py\nfrom typing import Any\nfrom typing_extensions import reveal_type\n\n\ndef returns_any() -> Any:\n return 42\n\n\ndef returns_int() -> int:\n my_int = returns_any()\n assert isinstance(my_int, int)\n reveal_type(my_int) # revealed: Any & int\n return my_int # no error: `Any & int` is a subtype of `int`\n```\n\n## Default level\n\nThis rule is disabled by default. It is intended for advanced users wanting additional soundness\nchecks from their type checker, not for users who have just started to use type checkers on their\nPython code.\n\n## See also\n\n- `unsound-yield` is a similar rule that triggers on unsound `yield` expressions rather than unsound\n `return` statements\n- `unsound-assignment` is a similar rule that triggers on unsound assignments\n\n[ann201]: https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/\n[ann202]: https://docs.astral.sh/ruff/rules/missing-return-type-private-function/\n[ann204]: https://docs.astral.sh/ruff/rules/missing-return-type-special-method/\n[ann205]: https://docs.astral.sh/ruff/rules/missing-return-type-static-method/\n[ann206]: https://docs.astral.sh/ruff/rules/missing-return-type-class-method/\n[assignable]: https://typing.python.org/en/latest/spec/glossary.html#term-assignable\n[fully-static]: https://typing.python.org/en/latest/spec/glossary.html#term-fully-static-type\n[mypy-strict]: https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-strict\n[no-any-return]: https://mypy.readthedocs.io/en/stable/error_code_list2.html#code-no-any-return\n[subtype]: https://typing.python.org/en/latest/spec/glossary.html#term-subtype\n[warn-return-any]: https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-warn-return-any", |
| "default": "ignore", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unsound-yield": { |
| "title": "detects yield expressions that unsoundly yield a type that is not a subtype of the generator's annotated yield type", |
| "description": "## What it does\n\nDetects `yield` and `yield from` expressions that unsoundly yield a type that is not a [subtype] of\nthe generator function's annotated yield type.\n\nThis lint is a stricter version of `invalid-yield`.\n\n## Why is this bad?\n\nBy default, type checkers consider a yielded value valid if its inferred type is [assignable] to the\ngenerator's annotated yield type. However, this makes it easy for incorrect types to percolate\nthrough your code unexpectedly due to a single expression being inferred as `Any`. This can easily\nlead to runtime errors that are not caught by the type checker:\n\n```py\nfrom typing import Any, Generator\n\n\ndef returns_any() -> Any:\n return \"not an integer\"\n\n\ndef integers() -> Generator[int]:\n # error: \"Unsound `yield`: `Any` is not a subtype of `int`\"\n yield returns_any()\n\n\n# Fails at runtime, even though the type checker infers `integers` as yielding only `int`s!\nsum(integers())\n```\n\nThis rule treats [\"fully static\"][fully-static] yield types as \"typed boundaries\" for your code.\nWith this rule enabled, ty would emit an error on the `yield returns_any()` statement in `integers`,\nsince the `returns_any()` call is inferred as having type `Any`, and `Any` is not a subtype of\n`int`. This helps prevent the unsoundness from spreading far from its original source (in this case,\nthe return type of the `returns_any` function).\n\nNote that this rule is only applied to functions annotated as yielding [fully static][fully-static]\ntypes. It will not trigger if `Any` or `Unknown` appear anywhere in your function's yield type,\neither implicitly or explicitly. It will still trigger on functions that have non-fully-static send\nand/or return types, however:\n\n```py\nfrom typing import Any, Generator\n\n\ndef returns_any() -> Any:\n return \"not an integer\"\n\n\ndef dynamic_yield_type() -> Generator[Any]:\n # no error\n yield returns_any()\n\n\ndef static_yield_type() -> Generator[int, Any, Any]:\n # error: \"Unsound `yield`: `Any` is not a subtype of `int`\"\n yield returns_any()\n```\n\nThis rule works especially well when combined with ty's `missing-type-argument` and\n`unsound-assignment` rules, as well as the Ruff rules [`ANN201`][ann201], [`ANN202`][ann202],\n[`ANN204`][ann204], [`ANN205`][ann205], and [`ANN206`][ann206]. Enabling all these rules at once\neffectively makes it much less likely that a `yield` expression can lead to unsoundness \"leaking\"\nout of a function unless that function has been *explicitly* annotated with a dynamic type in some\nway (`-> Generator[Any]` or `-> Generator[tuple[Any]]`, for example).\n\n## Examples\n\n```py\nfrom typing import Any, Iterator\n\n\ndef returns_any() -> Any:\n return \"foo\"\n\n\ndef any_iterator() -> Iterator[Any]:\n yield \"foo\"\n\n\ndef integers() -> Iterator[int]:\n # error: \"Unsound `yield`: `Any` is not a subtype of `int`\"\n yield returns_any()\n # error: \"Unsound `yield from`: `Any` is not a subtype of `int`\"\n yield from any_iterator()\n```\n\nNarrow the value before yielding it to fix the diagnostics:\n\n```py\nfrom typing import Any, Iterator\n\n\ndef returns_any() -> Any:\n return 42\n\n\ndef any_iterator() -> Iterator[Any]:\n yield \"foo\"\n\n\ndef integers() -> Iterator[int]:\n value = returns_any()\n assert isinstance(value, int)\n yield value\n\n for value in any_iterator():\n assert isinstance(value, int)\n yield value\n```\n\n## Default level\n\nThis rule is disabled by default. It is intended for users who want stricter soundness checks at\ngenerator boundaries.\n\n## See also\n\n- `unsound-return-statement` is a similar rule that triggers on unsound `return` statements rather\n than unsound `yield` expressions\n- `unsound-assignment` is a similar rule that triggers on unsound assignments\n\n[ann201]: https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/\n[ann202]: https://docs.astral.sh/ruff/rules/missing-return-type-private-function/\n[ann204]: https://docs.astral.sh/ruff/rules/missing-return-type-special-method/\n[ann205]: https://docs.astral.sh/ruff/rules/missing-return-type-static-method/\n[ann206]: https://docs.astral.sh/ruff/rules/missing-return-type-class-method/\n[assignable]: https://typing.python.org/en/latest/spec/glossary.html#term-assignable\n[fully-static]: https://typing.python.org/en/latest/spec/glossary.html#term-fully-static-type\n[subtype]: https://typing.python.org/en/latest/spec/glossary.html#term-subtype", |
| "default": "ignore", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unsupported-base": { |
| "title": "detects class bases that are unsupported as ty could not feasibly calculate the class's MRO", |
| "description": "## What it does\n\nChecks for class definitions that have bases which are unsupported by ty.\n\n## Why is this bad?\n\nIf a class has a base that is an instance of a complex type such as a union type, ty will not be\nable to resolve the [method resolution order] (MRO) for the class. This will lead to an inferior\nunderstanding of your codebase and unpredictable type-checking behavior.\n\n## Examples\n\n```python\nimport datetime\n\n\nclass A: ...\n\n\nclass B: ...\n\n\nif datetime.date.today().weekday() != 6:\n C = A\nelse:\n C = B\n\n\nclass D(C): ... # error: [unsupported-base]\n```\n\n[method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unsupported-bool-conversion": { |
| "title": "detects boolean conversion where the object incorrectly implements `__bool__`", |
| "description": "## What it does\n\nChecks for bool conversions where the object doesn't correctly implement `__bool__`.\n\n## Why is this bad?\n\nIf an exception is raised when you attempt to evaluate the truthiness of an object, using the object\nin a boolean context will fail at runtime.\n\n## Examples\n\n```python\nclass NotBoolable:\n __bool__ = None\n\n def __lt__(self, other: object) -> \"NotBoolable\":\n return self\n\n\nb1 = NotBoolable()\nb2 = NotBoolable()\n\n# exception raised here\nif b1: # error\n pass\n\n# exception raised here\nb1 and b2 # error\n# exception raised here\nnot b1 # error\n\n# A chained comparison converts the result of `b1 < b2` to bool.\n# exception raised here\nb1 < b2 < b1 # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unsupported-dynamic-base": { |
| "title": "detects dynamic class bases that are unsupported as ty could not feasibly calculate the class's MRO", |
| "description": "## What it does\n\nChecks for dynamic class definitions (using `type()`) that have bases which are unsupported by ty.\n\nThis is equivalent to `unsupported-base` but applies to classes created via `type()` rather than\n`class` statements.\n\n## Why is this bad?\n\nIf a dynamically created class has a base that is an unsupported type such as `type[T]`, ty will not\nbe able to resolve the [method resolution order] (MRO) for the class. This may lead to an inferior\nunderstanding of your codebase and unpredictable type-checking behavior.\n\n## Default level\n\nThis rule is disabled by default because it will not cause a runtime error, and may be noisy on\ncodebases that use `type()` in highly dynamic ways.\n\n## Examples\n\n```python\nclass Base: ...\n\n\ndef factory(base: type[Base]) -> type:\n # `base` has type `type[Base]`, not `type[Base]` itself\n return type(\"Dynamic\", (base,), {}) # error: [unsupported-dynamic-base]\n```\n\n[method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order", |
| "default": "ignore", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unsupported-operator": { |
| "title": "detects binary, unary, or comparison expressions where the operands don't support the operator", |
| "description": "## What it does\n\nChecks for binary expressions, comparisons, and unary expressions where the operands don't support\nthe operator.\n\n## Why is this bad?\n\nAttempting to use an unsupported operator will raise a `TypeError` at runtime.\n\n## Examples\n\n```python\nclass A: ...\n\n\n# TypeError: unsupported operand type(s) for +: 'A' and 'A'\nA() + A() # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unused-awaitable": { |
| "title": "detects awaitable objects that are used as expression statements without being awaited", |
| "description": "## What it does\n\nChecks for awaitable objects (such as coroutines) used as expression statements without being\nawaited.\n\n## Why is this bad?\n\nCalling an `async def` function returns a coroutine object. If the coroutine is never awaited, the\nbody of the async function will never execute, which is almost always a bug. Python emits a\n`RuntimeWarning: coroutine was never awaited` at runtime in this case.\n\n## Examples\n\n```python\nasync def fetch_data() -> str:\n return \"data\"\n\n\nasync def main() -> None:\n # Warning: coroutine is not awaited\n fetch_data() # error\n await fetch_data() # OK\n```", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unused-ignore-comment": { |
| "title": "detects unused `ty: ignore` comments", |
| "description": "## What it does\n\nChecks for `ty: ignore` directives that are no longer applicable.\n\n## Why is this bad?\n\nA `ty: ignore` directive that no longer matches any diagnostic violations is likely included by\nmistake, and should be removed to avoid confusion.\n\n## Examples\n\n```py\n# error\na = 20 / 2 # ty: ignore[division-by-zero]\n```\n\nUse instead:\n\n```py\na = 20 / 2\n```\n\n## Options\n\nSet\n[`analysis.respect-type-ignore-comments`](https://docs.astral.sh/ty/reference/configuration/#respect-type-ignore-comments)\nto `false` to prevent this rule from reporting unused `type: ignore` comments.", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "unused-type-ignore-comment": { |
| "title": "detects unused `type: ignore` comments", |
| "description": "## What it does\n\nChecks for `type: ignore` directives that are no longer applicable.\n\n## Why is this bad?\n\nA `type: ignore` directive that no longer matches any diagnostic violations is likely included by\nmistake, and should be removed to avoid confusion.\n\n## Examples\n\n```py\n# error\na = 20 / 2 # type: ignore\n```\n\nUse instead:\n\n```py\na = 20 / 2\n```\n\n## Options\n\nThis rule is skipped if\n[`analysis.respect-type-ignore-comments`](https://docs.astral.sh/ty/reference/configuration/#respect-type-ignore-comments)\nto `false`.", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "useless-overload-body": { |
| "title": "detects `@overload`-decorated functions with non-stub bodies", |
| "description": "## What it does\n\nChecks for various `@overload`-decorated functions that have non-stub bodies.\n\n## Why is this bad?\n\nFunctions decorated with `@overload` are ignored at runtime; they are overridden by the\nimplementation function that follows the series of overloads. While it is not illegal to provide a\nbody for an `@overload`-decorated function, it may indicate a misunderstanding of how the\n`@overload` decorator works.\n\n## Example\n\n```toml\n[environment]\npython-version = \"3.11\"\n```\n\n```py\nfrom typing import overload\n\n\n@overload\ndef foo(x: int) -> int:\n # will never be executed\n return x + 1 # error\n\n\n@overload\ndef foo(x: str) -> str:\n # will never be executed\n return \"Oh no, got a string\" # error\n\n\ndef foo(x: int | str) -> int | str:\n raise Exception(\"unexpected type encountered\")\n```\n\nUse instead:\n\n```py\nfrom typing import assert_never, overload\n\n\n@overload\ndef foo(x: int) -> int: ...\n\n\n@overload\ndef foo(x: str) -> str: ...\n\n\ndef foo(x: int | str) -> int | str:\n if isinstance(x, int):\n return x + 1\n elif isinstance(x, str):\n return \"Oh no, got a string\"\n else:\n assert_never(x)\n```\n\n## References\n\n- [Python documentation: `@overload`](https://docs.python.org/3/library/typing.html#typing.overload)", |
| "default": "warn", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| }, |
| "zero-stepsize-in-slice": { |
| "title": "detects a slice step size of zero", |
| "description": "## What it does\n\nChecks for a step size of zero in slices when the operation is known to fail.\n\n## Why is this bad?\n\nPython's built-in sequence types raise a `ValueError` when sliced with a step size of zero.\n\n## Known problems\n\nThis check is not exhaustive. It reports zero-step slices for certain built-in sequence types where\nthe operation is known to fail. A custom `__getitem__` implementation can accept or reject such a\nslice, so ty cannot detect every runtime failure.\n\n## Examples\n\n```python\nvalues = list(range(10))\n# ValueError: slice step cannot be zero\nvalues[1:10:0] # error\n\ntuple_values = (1, 2, 3)\n# ValueError: slice step cannot be zero\ntuple_values[1:10:0] # error\n```", |
| "default": "error", |
| "oneOf": [ |
| { |
| "$ref": "#/definitions/Level" |
| } |
| ] |
| } |
| }, |
| "additionalProperties": { |
| "$ref": "#/definitions/Level" |
| } |
| }, |
| "SrcOptions": { |
| "type": "object", |
| "properties": { |
| "exclude": { |
| "description": "A list of file and directory patterns to exclude from type checking.\n\nPatterns follow a syntax similar to `.gitignore`:\n\n- `./src/` matches only a directory\n- `./src` matches both files and directories\n- `src` matches files or directories named `src`\n- `*` matches any (possibly empty) sequence of characters (except `/`).\n- `**` matches zero or more path components.\n This sequence **must** form a single path component, so both `**a` and `b**` are invalid and will result in an error.\n A sequence of more than two consecutive `*` characters is also invalid.\n- `?` matches any single character except `/`\n- `[abc]` matches any character inside the brackets. Character sequences can also specify ranges of characters, as ordered by Unicode,\n so e.g. `[0-9]` specifies any character between `0` and `9` inclusive. An unclosed bracket is invalid.\n- `!pattern` negates a pattern (undoes the exclusion of files that would otherwise be excluded)\n\nAll paths are anchored relative to the project root (`src` only\nmatches `<project_root>/src` and not `<project_root>/test/src`).\nTo exclude any directory or file named `src`, use `**/src` instead.\n\nBy default, ty excludes commonly ignored directories:\n\n- `**/.bzr/`\n- `**/.direnv/`\n- `**/.eggs/`\n- `**/.git/`\n- `**/.git-rewrite/`\n- `**/.hg/`\n- `**/.mypy_cache/`\n- `**/.nox/`\n- `**/.pants.d/`\n- `**/.pytype/`\n- `**/.ruff_cache/`\n- `**/.svn/`\n- `**/.tox/`\n- `**/.venv/`\n- `**/__pypackages__/`\n- `**/_build/`\n- `**/buck-out/`\n- `**/dist/`\n- `**/node_modules/`\n- `**/venv/`\n\nYou can override any default exclude by using a negated pattern. For example,\nto re-include `dist` use `exclude = [\"!dist\"]`", |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/Array_of_string" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| }, |
| "exclude-scripts": { |
| "description": "Whether to exclude files containing PEP 723 inline script metadata unless they are\nexplicitly passed on the command line.", |
| "type": [ |
| "boolean", |
| "null" |
| ] |
| }, |
| "include": { |
| "description": "A list of files and directories to check. The `include` option\nfollows a similar syntax to `.gitignore` but reversed:\nIncluding a file or directory will make it so that it (and its contents)\nare type checked.\n\n- `./src/` matches only a directory\n- `./src` matches both files and directories\n- `src` matches a file or directory named `src`\n- `*` matches any (possibly empty) sequence of characters (except `/`).\n- `**` matches zero or more path components.\n This sequence **must** form a single path component, so both `**a` and `b**` are invalid and will result in an error.\n A sequence of more than two consecutive `*` characters is also invalid.\n- `?` matches any single character except `/`\n- `[abc]` matches any character inside the brackets. Character sequences can also specify ranges of characters, as ordered by Unicode,\n so e.g. `[0-9]` specifies any character between `0` and `9` inclusive. An unclosed bracket is invalid.\n\nAll paths are anchored relative to the project root (`src` only\nmatches `<project_root>/src` and not `<project_root>/test/src`).\n\n`exclude` takes precedence over `include`.", |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/Array_of_string" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| }, |
| "respect-ignore-files": { |
| "description": "Whether to automatically exclude files that are ignored by `.ignore`,\n`.gitignore`, `.git/info/exclude`, and global `gitignore` files.\nEnabled by default.", |
| "type": [ |
| "boolean", |
| "null" |
| ] |
| } |
| }, |
| "additionalProperties": false |
| }, |
| "SupportedPythonVersion": { |
| "description": "A Python version explicitly supported by ty configuration and CLI parsing.", |
| "oneOf": [ |
| { |
| "description": "Python 3.7", |
| "type": "string", |
| "const": "3.7" |
| }, |
| { |
| "description": "Python 3.8", |
| "type": "string", |
| "const": "3.8" |
| }, |
| { |
| "description": "Python 3.9", |
| "type": "string", |
| "const": "3.9" |
| }, |
| { |
| "description": "Python 3.10", |
| "type": "string", |
| "const": "3.10" |
| }, |
| { |
| "description": "Python 3.11", |
| "type": "string", |
| "const": "3.11" |
| }, |
| { |
| "description": "Python 3.12", |
| "type": "string", |
| "const": "3.12" |
| }, |
| { |
| "description": "Python 3.13", |
| "type": "string", |
| "const": "3.13" |
| }, |
| { |
| "description": "Python 3.14", |
| "type": "string", |
| "const": "3.14" |
| }, |
| { |
| "description": "Python 3.15", |
| "type": "string", |
| "const": "3.15" |
| } |
| ] |
| }, |
| "SystemPathBuf": { |
| "description": "An owned, mutable path on [`System`](`super::System`) (akin to [`String`]).\n\nThe path is guaranteed to be valid UTF-8.", |
| "type": "string" |
| }, |
| "TerminalOptions": { |
| "type": "object", |
| "properties": { |
| "error-on-warning": { |
| "description": "Use exit code 1, even if all diagnostics only had `warning` severity.\n\nDefaults to `true`.", |
| "type": [ |
| "boolean", |
| "null" |
| ] |
| }, |
| "output-format": { |
| "description": "The format to use for printing diagnostic messages.\n\nDefaults to `full`.", |
| "anyOf": [ |
| { |
| "$ref": "#/definitions/OutputFormat" |
| }, |
| { |
| "type": "null" |
| } |
| ] |
| } |
| }, |
| "additionalProperties": false |
| }, |
| "string": { |
| "type": "string" |
| } |
| } |
| } |