mirror of https://github.com/astral-sh/ruff
Merge branch 'main' into dcreager/callable-return
* main: [ty] Document `TY_CONFIG_FILE` (#22001) [ty] Cache `KnownClass::to_class_literal` (#22000) [ty] Fix benchmark assertion (#22003) Add uv and ty to the Ruff README (#21996) [ty] Infer precise types for `isinstance(…)` calls involving typevars (#21999) [ty] Use `FxHashMap` in `Signature::has_relation_to` (#21997) [ty] Avoid enforcing standalone expression for tests in f-strings (#21967) [ty] Use `title` for configuration code fences in ty reference documentation (#21992)
This commit is contained in:
commit
75b851638d
|
|
@ -57,8 +57,11 @@ Ruff is extremely actively developed and used in major open-source projects like
|
||||||
|
|
||||||
...and [many more](#whos-using-ruff).
|
...and [many more](#whos-using-ruff).
|
||||||
|
|
||||||
Ruff is backed by [Astral](https://astral.sh). Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff),
|
Ruff is backed by [Astral](https://astral.sh), the creators of
|
||||||
or the original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
[uv](https://github.com/astral-sh/uv) and [ty](https://github.com/astral-sh/ty).
|
||||||
|
|
||||||
|
Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff), or the
|
||||||
|
original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||||
|
|
||||||
## Testimonials
|
## Testimonials
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -166,8 +166,9 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
let _ = writeln!(output, "**Type**: `{}`", field.value_type);
|
let _ = writeln!(output, "**Type**: `{}`", field.value_type);
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
output.push_str("**Example usage** (`pyproject.toml`):\n\n");
|
output.push_str("**Example usage**:\n\n");
|
||||||
output.push_str(&format_example(
|
output.push_str(&format_example(
|
||||||
|
"pyproject.toml",
|
||||||
&format_header(
|
&format_header(
|
||||||
field.scope,
|
field.scope,
|
||||||
field.example,
|
field.example,
|
||||||
|
|
@ -179,11 +180,11 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_example(header: &str, content: &str) -> String {
|
fn format_example(title: &str, header: &str, content: &str) -> String {
|
||||||
if header.is_empty() {
|
if header.is_empty() {
|
||||||
format!("```toml\n{content}\n```\n",)
|
format!("```toml title=\"{title}\"\n{content}\n```\n",)
|
||||||
} else {
|
} else {
|
||||||
format!("```toml\n{header}\n{content}\n```\n",)
|
format!("```toml title=\"{title}\"\n{header}\n{content}\n```\n",)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ Valid severities are:
|
||||||
|
|
||||||
**Type**: `dict[RuleName, "ignore" | "warn" | "error"]`
|
**Type**: `dict[RuleName, "ignore" | "warn" | "error"]`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage**:
|
||||||
|
|
||||||
```toml
|
```toml title="pyproject.toml"
|
||||||
[tool.ty.rules]
|
[tool.ty.rules]
|
||||||
possibly-unresolved-reference = "warn"
|
possibly-unresolved-reference = "warn"
|
||||||
division-by-zero = "ignore"
|
division-by-zero = "ignore"
|
||||||
|
|
@ -45,9 +45,9 @@ configuration setting.
|
||||||
|
|
||||||
**Type**: `list[str]`
|
**Type**: `list[str]`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage**:
|
||||||
|
|
||||||
```toml
|
```toml title="pyproject.toml"
|
||||||
[tool.ty.environment]
|
[tool.ty.environment]
|
||||||
extra-paths = ["./shared/my-search-path"]
|
extra-paths = ["./shared/my-search-path"]
|
||||||
```
|
```
|
||||||
|
|
@ -76,9 +76,9 @@ This option can be used to point to virtual or system Python environments.
|
||||||
|
|
||||||
**Type**: `str`
|
**Type**: `str`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage**:
|
||||||
|
|
||||||
```toml
|
```toml title="pyproject.toml"
|
||||||
[tool.ty.environment]
|
[tool.ty.environment]
|
||||||
python = "./custom-venv-location/.venv"
|
python = "./custom-venv-location/.venv"
|
||||||
```
|
```
|
||||||
|
|
@ -103,9 +103,9 @@ If no platform is specified, ty will use the current platform:
|
||||||
|
|
||||||
**Type**: `"win32" | "darwin" | "android" | "ios" | "linux" | "all" | str`
|
**Type**: `"win32" | "darwin" | "android" | "ios" | "linux" | "all" | str`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage**:
|
||||||
|
|
||||||
```toml
|
```toml title="pyproject.toml"
|
||||||
[tool.ty.environment]
|
[tool.ty.environment]
|
||||||
# Tailor type stubs and conditionalized type definitions to windows.
|
# Tailor type stubs and conditionalized type definitions to windows.
|
||||||
python-platform = "win32"
|
python-platform = "win32"
|
||||||
|
|
@ -137,9 +137,9 @@ to reflect the differing contents of the standard library across Python versions
|
||||||
|
|
||||||
**Type**: `"3.7" | "3.8" | "3.9" | "3.10" | "3.11" | "3.12" | "3.13" | "3.14" | <major>.<minor>`
|
**Type**: `"3.7" | "3.8" | "3.9" | "3.10" | "3.11" | "3.12" | "3.13" | "3.14" | <major>.<minor>`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage**:
|
||||||
|
|
||||||
```toml
|
```toml title="pyproject.toml"
|
||||||
[tool.ty.environment]
|
[tool.ty.environment]
|
||||||
python-version = "3.12"
|
python-version = "3.12"
|
||||||
```
|
```
|
||||||
|
|
@ -165,9 +165,9 @@ it will also be included in the first party search path.
|
||||||
|
|
||||||
**Type**: `list[str]`
|
**Type**: `list[str]`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage**:
|
||||||
|
|
||||||
```toml
|
```toml title="pyproject.toml"
|
||||||
[tool.ty.environment]
|
[tool.ty.environment]
|
||||||
# Multiple directories (priority order)
|
# Multiple directories (priority order)
|
||||||
root = ["./src", "./lib", "./vendor"]
|
root = ["./src", "./lib", "./vendor"]
|
||||||
|
|
@ -185,9 +185,9 @@ bundled as a zip file in the binary
|
||||||
|
|
||||||
**Type**: `str`
|
**Type**: `str`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage**:
|
||||||
|
|
||||||
```toml
|
```toml title="pyproject.toml"
|
||||||
[tool.ty.environment]
|
[tool.ty.environment]
|
||||||
typeshed = "/path/to/custom/typeshed"
|
typeshed = "/path/to/custom/typeshed"
|
||||||
```
|
```
|
||||||
|
|
@ -240,9 +240,9 @@ If not specified, defaults to `[]` (excludes no files).
|
||||||
|
|
||||||
**Type**: `list[str]`
|
**Type**: `list[str]`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage**:
|
||||||
|
|
||||||
```toml
|
```toml title="pyproject.toml"
|
||||||
[[tool.ty.overrides]]
|
[[tool.ty.overrides]]
|
||||||
exclude = [
|
exclude = [
|
||||||
"generated",
|
"generated",
|
||||||
|
|
@ -268,9 +268,9 @@ If not specified, defaults to `["**"]` (matches all files).
|
||||||
|
|
||||||
**Type**: `list[str]`
|
**Type**: `list[str]`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage**:
|
||||||
|
|
||||||
```toml
|
```toml title="pyproject.toml"
|
||||||
[[tool.ty.overrides]]
|
[[tool.ty.overrides]]
|
||||||
include = [
|
include = [
|
||||||
"src",
|
"src",
|
||||||
|
|
@ -292,9 +292,9 @@ severity levels or disable them entirely.
|
||||||
|
|
||||||
**Type**: `dict[RuleName, "ignore" | "warn" | "error"]`
|
**Type**: `dict[RuleName, "ignore" | "warn" | "error"]`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage**:
|
||||||
|
|
||||||
```toml
|
```toml title="pyproject.toml"
|
||||||
[[tool.ty.overrides]]
|
[[tool.ty.overrides]]
|
||||||
include = ["src"]
|
include = ["src"]
|
||||||
|
|
||||||
|
|
@ -358,9 +358,9 @@ to re-include `dist` use `exclude = ["!dist"]`
|
||||||
|
|
||||||
**Type**: `list[str]`
|
**Type**: `list[str]`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage**:
|
||||||
|
|
||||||
```toml
|
```toml title="pyproject.toml"
|
||||||
[tool.ty.src]
|
[tool.ty.src]
|
||||||
exclude = [
|
exclude = [
|
||||||
"generated",
|
"generated",
|
||||||
|
|
@ -399,9 +399,9 @@ matches `<project_root>/src` and not `<project_root>/test/src`).
|
||||||
|
|
||||||
**Type**: `list[str]`
|
**Type**: `list[str]`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage**:
|
||||||
|
|
||||||
```toml
|
```toml title="pyproject.toml"
|
||||||
[tool.ty.src]
|
[tool.ty.src]
|
||||||
include = [
|
include = [
|
||||||
"src",
|
"src",
|
||||||
|
|
@ -421,9 +421,9 @@ Enabled by default.
|
||||||
|
|
||||||
**Type**: `bool`
|
**Type**: `bool`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage**:
|
||||||
|
|
||||||
```toml
|
```toml title="pyproject.toml"
|
||||||
[tool.ty.src]
|
[tool.ty.src]
|
||||||
respect-ignore-files = false
|
respect-ignore-files = false
|
||||||
```
|
```
|
||||||
|
|
@ -450,9 +450,9 @@ it will also be included in the first party search path.
|
||||||
|
|
||||||
**Type**: `str`
|
**Type**: `str`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage**:
|
||||||
|
|
||||||
```toml
|
```toml title="pyproject.toml"
|
||||||
[tool.ty.src]
|
[tool.ty.src]
|
||||||
root = "./app"
|
root = "./app"
|
||||||
```
|
```
|
||||||
|
|
@ -471,9 +471,9 @@ Defaults to `false`.
|
||||||
|
|
||||||
**Type**: `bool`
|
**Type**: `bool`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage**:
|
||||||
|
|
||||||
```toml
|
```toml title="pyproject.toml"
|
||||||
[tool.ty.terminal]
|
[tool.ty.terminal]
|
||||||
# Error if ty emits any warning-level diagnostics.
|
# Error if ty emits any warning-level diagnostics.
|
||||||
error-on-warning = true
|
error-on-warning = true
|
||||||
|
|
@ -491,9 +491,9 @@ Defaults to `full`.
|
||||||
|
|
||||||
**Type**: `full | concise`
|
**Type**: `full | concise`
|
||||||
|
|
||||||
**Example usage** (`pyproject.toml`):
|
**Example usage**:
|
||||||
|
|
||||||
```toml
|
```toml title="pyproject.toml"
|
||||||
[tool.ty.terminal]
|
[tool.ty.terminal]
|
||||||
output-format = "concise"
|
output-format = "concise"
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,15 @@
|
||||||
|
|
||||||
ty defines and respects the following environment variables:
|
ty defines and respects the following environment variables:
|
||||||
|
|
||||||
|
### `TY_CONFIG_FILE`
|
||||||
|
|
||||||
|
Path to a `ty.toml` configuration file to use.
|
||||||
|
|
||||||
|
When set, ty will use this file for configuration instead of
|
||||||
|
discovering configuration files automatically.
|
||||||
|
|
||||||
|
Equivalent to the `--config-file` command-line argument.
|
||||||
|
|
||||||
### `TY_LOG`
|
### `TY_LOG`
|
||||||
|
|
||||||
If set, ty will use this value as the log level for its `--verbose` output.
|
If set, ty will use this value as the log level for its `--verbose` output.
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use ty_combine::Combine;
|
||||||
use ty_project::metadata::options::{EnvironmentOptions, Options, SrcOptions, TerminalOptions};
|
use ty_project::metadata::options::{EnvironmentOptions, Options, SrcOptions, TerminalOptions};
|
||||||
use ty_project::metadata::value::{RangedValue, RelativeGlobPattern, RelativePathBuf, ValueSource};
|
use ty_project::metadata::value::{RangedValue, RelativeGlobPattern, RelativePathBuf, ValueSource};
|
||||||
use ty_python_semantic::lint;
|
use ty_python_semantic::lint;
|
||||||
|
use ty_static::EnvVars;
|
||||||
|
|
||||||
// Configures Clap v3-style help menu colors
|
// Configures Clap v3-style help menu colors
|
||||||
const STYLES: Styles = Styles::styled()
|
const STYLES: Styles = Styles::styled()
|
||||||
|
|
@ -121,7 +122,7 @@ pub(crate) struct CheckCommand {
|
||||||
/// The path to a `ty.toml` file to use for configuration.
|
/// The path to a `ty.toml` file to use for configuration.
|
||||||
///
|
///
|
||||||
/// While ty configuration can be included in a `pyproject.toml` file, it is not allowed in this context.
|
/// While ty configuration can be included in a `pyproject.toml` file, it is not allowed in this context.
|
||||||
#[arg(long, env = "TY_CONFIG_FILE", value_name = "PATH")]
|
#[arg(long, env = EnvVars::TY_CONFIG_FILE, value_name = "PATH")]
|
||||||
pub(crate) config_file: Option<SystemPathBuf>,
|
pub(crate) config_file: Option<SystemPathBuf>,
|
||||||
|
|
||||||
/// The format to use for printing diagnostic messages.
|
/// The format to use for printing diagnostic messages.
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,20 @@ The expressions in these string annotations aren't valid expressions in this con
|
||||||
shouldn't panic.
|
shouldn't panic.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
# Regression test for https://github.com/astral-sh/ty/issues/1865
|
||||||
|
# error: [fstring-type-annotation]
|
||||||
|
stringified_fstring_with_conditional: "f'{1 if 1 else 1}'"
|
||||||
|
# error: [fstring-type-annotation]
|
||||||
|
stringified_fstring_with_boolean_expression: "f'{1 or 2}'"
|
||||||
|
# error: [fstring-type-annotation]
|
||||||
|
stringified_fstring_with_generator_expression: "f'{(i for i in range(5))}'"
|
||||||
|
# error: [fstring-type-annotation]
|
||||||
|
stringified_fstring_with_list_comprehension: "f'{[i for i in range(5)]}'"
|
||||||
|
# error: [fstring-type-annotation]
|
||||||
|
stringified_fstring_with_dict_comprehension: "f'{ {i: i for i in range(5)} }'"
|
||||||
|
# error: [fstring-type-annotation]
|
||||||
|
stringified_fstring_with_set_comprehension: "f'{ {i for i in range(5)} }'"
|
||||||
|
|
||||||
a: "1 or 2"
|
a: "1 or 2"
|
||||||
b: "(x := 1)"
|
b: "(x := 1)"
|
||||||
# error: [invalid-type-form]
|
# error: [invalid-type-form]
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@ but fall back to `bool` otherwise.
|
||||||
```py
|
```py
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from types import FunctionType
|
from types import FunctionType
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
class Answer(Enum):
|
class Answer(Enum):
|
||||||
NO = 0
|
NO = 0
|
||||||
|
|
@ -137,6 +138,7 @@ reveal_type(isinstance("", int)) # revealed: bool
|
||||||
|
|
||||||
class A: ...
|
class A: ...
|
||||||
class SubclassOfA(A): ...
|
class SubclassOfA(A): ...
|
||||||
|
class OtherSubclassOfA(A): ...
|
||||||
class B: ...
|
class B: ...
|
||||||
|
|
||||||
reveal_type(isinstance(A, type)) # revealed: Literal[True]
|
reveal_type(isinstance(A, type)) # revealed: Literal[True]
|
||||||
|
|
@ -161,6 +163,29 @@ def _(x: A | B, y: list[int]):
|
||||||
else:
|
else:
|
||||||
reveal_type(x) # revealed: B & ~A
|
reveal_type(x) # revealed: B & ~A
|
||||||
reveal_type(isinstance(x, B)) # revealed: Literal[True]
|
reveal_type(isinstance(x, B)) # revealed: Literal[True]
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
T_bound_A = TypeVar("T_bound_A", bound=A)
|
||||||
|
T_constrained = TypeVar("T_constrained", SubclassOfA, OtherSubclassOfA)
|
||||||
|
|
||||||
|
def _(
|
||||||
|
x: T,
|
||||||
|
x_bound_a: T_bound_A,
|
||||||
|
x_constrained_sub_a: T_constrained,
|
||||||
|
):
|
||||||
|
reveal_type(isinstance(x, object)) # revealed: Literal[True]
|
||||||
|
reveal_type(isinstance(x, A)) # revealed: bool
|
||||||
|
|
||||||
|
reveal_type(isinstance(x_bound_a, object)) # revealed: Literal[True]
|
||||||
|
reveal_type(isinstance(x_bound_a, A)) # revealed: Literal[True]
|
||||||
|
reveal_type(isinstance(x_bound_a, SubclassOfA)) # revealed: bool
|
||||||
|
reveal_type(isinstance(x_bound_a, B)) # revealed: bool
|
||||||
|
|
||||||
|
reveal_type(isinstance(x_constrained_sub_a, object)) # revealed: Literal[True]
|
||||||
|
reveal_type(isinstance(x_constrained_sub_a, A)) # revealed: Literal[True]
|
||||||
|
reveal_type(isinstance(x_constrained_sub_a, SubclassOfA)) # revealed: bool
|
||||||
|
reveal_type(isinstance(x_constrained_sub_a, OtherSubclassOfA)) # revealed: bool
|
||||||
|
reveal_type(isinstance(x_constrained_sub_a, B)) # revealed: bool
|
||||||
```
|
```
|
||||||
|
|
||||||
Certain special forms in the typing module are not instances of `type`, so are strictly-speaking
|
Certain special forms in the typing module are not instances of `type`, so are strictly-speaking
|
||||||
|
|
|
||||||
|
|
@ -522,6 +522,11 @@ impl<'db> SemanticIndex<'db> {
|
||||||
self.scopes_by_node[&node.node_key()]
|
self.scopes_by_node[&node.node_key()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the id of the scope that `node` creates, if it exists.
|
||||||
|
pub(crate) fn try_node_scope(&self, node: NodeWithScopeRef) -> Option<FileScopeId> {
|
||||||
|
self.scopes_by_node.get(&node.node_key()).copied()
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks if there is an import of `__future__.annotations` in the global scope, which affects
|
/// Checks if there is an import of `__future__.annotations` in the global scope, which affects
|
||||||
/// the logic for type inference.
|
/// the logic for type inference.
|
||||||
pub(super) fn has_future_annotations(&self) -> bool {
|
pub(super) fn has_future_annotations(&self) -> bool {
|
||||||
|
|
|
||||||
|
|
@ -5077,25 +5077,39 @@ impl KnownClass {
|
||||||
///
|
///
|
||||||
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
||||||
pub(crate) fn try_to_class_literal(self, db: &dyn Db) -> Option<ClassLiteral<'_>> {
|
pub(crate) fn try_to_class_literal(self, db: &dyn Db) -> Option<ClassLiteral<'_>> {
|
||||||
// a cache of the `KnownClass`es that we have already failed to lookup in typeshed
|
#[salsa::interned(heap_size=ruff_memory_usage::heap_size)]
|
||||||
// (and therefore that we've already logged a warning for)
|
struct KnownClassArgument {
|
||||||
static MESSAGES: LazyLock<Mutex<FxHashSet<KnownClass>>> = LazyLock::new(Mutex::default);
|
class: KnownClass,
|
||||||
|
}
|
||||||
|
|
||||||
self.try_to_class_literal_without_logging(db)
|
fn known_class_to_class_literal_initial<'db>(
|
||||||
|
_db: &'db dyn Db,
|
||||||
|
_id: salsa::Id,
|
||||||
|
_class: KnownClassArgument<'db>,
|
||||||
|
) -> Option<ClassLiteral<'db>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[salsa::tracked(cycle_initial=known_class_to_class_literal_initial, heap_size=ruff_memory_usage::heap_size)]
|
||||||
|
fn known_class_to_class_literal<'db>(
|
||||||
|
db: &'db dyn Db,
|
||||||
|
class: KnownClassArgument<'db>,
|
||||||
|
) -> Option<ClassLiteral<'db>> {
|
||||||
|
let class = class.class(db);
|
||||||
|
class
|
||||||
|
.try_to_class_literal_without_logging(db)
|
||||||
.or_else(|lookup_error| {
|
.or_else(|lookup_error| {
|
||||||
if MESSAGES.lock().unwrap().insert(self) {
|
|
||||||
if matches!(
|
if matches!(
|
||||||
lookup_error,
|
lookup_error,
|
||||||
KnownClassLookupError::ClassPossiblyUnbound { .. }
|
KnownClassLookupError::ClassPossiblyUnbound { .. }
|
||||||
) {
|
) {
|
||||||
tracing::info!("{}", lookup_error.display(db, self));
|
tracing::info!("{}", lookup_error.display(db, class));
|
||||||
} else {
|
} else {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"{}. Falling back to `Unknown` for the symbol instead.",
|
"{}. Falling back to `Unknown` for the symbol instead.",
|
||||||
lookup_error.display(db, self)
|
lookup_error.display(db, class)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
match lookup_error {
|
match lookup_error {
|
||||||
KnownClassLookupError::ClassPossiblyUnbound { class_literal, .. } => {
|
KnownClassLookupError::ClassPossiblyUnbound { class_literal, .. } => {
|
||||||
|
|
@ -5108,6 +5122,9 @@ impl KnownClass {
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
known_class_to_class_literal(db, KnownClassArgument::new(db, self))
|
||||||
|
}
|
||||||
|
|
||||||
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`] representing that class-literal.
|
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`] representing that class-literal.
|
||||||
///
|
///
|
||||||
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,8 @@ use crate::types::{
|
||||||
ClassBase, ClassLiteral, ClassType, DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor,
|
ClassBase, ClassLiteral, ClassType, DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor,
|
||||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType,
|
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType,
|
||||||
NormalizedVisitor, SpecialFormType, SubclassOfInner, SubclassOfType, Truthiness, Type,
|
NormalizedVisitor, SpecialFormType, SubclassOfInner, SubclassOfType, Truthiness, Type,
|
||||||
TypeContext, TypeMapping, TypeRelation, UnionBuilder, binding_type, definition_expression_type,
|
TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, UnionBuilder, binding_type,
|
||||||
infer_definition_types, walk_signature,
|
definition_expression_type, infer_definition_types, walk_signature,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
|
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
|
||||||
|
|
||||||
|
|
@ -1268,6 +1268,19 @@ fn is_instance_truthiness<'db>(
|
||||||
|
|
||||||
Type::TypeAlias(alias) => is_instance_truthiness(db, alias.value_type(db), class),
|
Type::TypeAlias(alias) => is_instance_truthiness(db, alias.value_type(db), class),
|
||||||
|
|
||||||
|
Type::TypeVar(bound_typevar) => match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||||
|
None => is_instance_truthiness(db, Type::object(), class),
|
||||||
|
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||||
|
is_instance_truthiness(db, bound, class)
|
||||||
|
}
|
||||||
|
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => always_true_if(
|
||||||
|
constraints
|
||||||
|
.elements(db)
|
||||||
|
.iter()
|
||||||
|
.all(|c| is_instance_truthiness(db, *c, class).is_always_true()),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
Type::BoundMethod(..)
|
Type::BoundMethod(..)
|
||||||
| Type::KnownBoundMethod(..)
|
| Type::KnownBoundMethod(..)
|
||||||
| Type::WrapperDescriptor(..)
|
| Type::WrapperDescriptor(..)
|
||||||
|
|
@ -1281,7 +1294,6 @@ fn is_instance_truthiness<'db>(
|
||||||
| Type::PropertyInstance(..)
|
| Type::PropertyInstance(..)
|
||||||
| Type::AlwaysTruthy
|
| Type::AlwaysTruthy
|
||||||
| Type::AlwaysFalsy
|
| Type::AlwaysFalsy
|
||||||
| Type::TypeVar(..)
|
|
||||||
| Type::BoundSuper(..)
|
| Type::BoundSuper(..)
|
||||||
| Type::TypeIs(..)
|
| Type::TypeIs(..)
|
||||||
| Type::Callable(..)
|
| Type::Callable(..)
|
||||||
|
|
|
||||||
|
|
@ -7926,7 +7926,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
let Some(first_comprehension) = comprehensions_iter.next() else {
|
let Some(first_comprehension) = comprehensions_iter.next() else {
|
||||||
unreachable!("Comprehension must contain at least one generator");
|
unreachable!("Comprehension must contain at least one generator");
|
||||||
};
|
};
|
||||||
self.infer_standalone_expression(&first_comprehension.iter, TypeContext::default());
|
self.infer_maybe_standalone_expression(&first_comprehension.iter, TypeContext::default());
|
||||||
|
|
||||||
if first_comprehension.is_async {
|
if first_comprehension.is_async {
|
||||||
EvaluationMode::Async
|
EvaluationMode::Async
|
||||||
|
|
@ -7946,9 +7946,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
|
|
||||||
let evaluation_mode = self.infer_first_comprehension_iter(generators);
|
let evaluation_mode = self.infer_first_comprehension_iter(generators);
|
||||||
|
|
||||||
let scope_id = self
|
let Some(scope_id) = self
|
||||||
.index
|
.index
|
||||||
.node_scope(NodeWithScopeRef::GeneratorExpression(generator));
|
.try_node_scope(NodeWithScopeRef::GeneratorExpression(generator))
|
||||||
|
else {
|
||||||
|
return Type::unknown();
|
||||||
|
};
|
||||||
let scope = scope_id.to_scope_id(self.db(), self.file());
|
let scope = scope_id.to_scope_id(self.db(), self.file());
|
||||||
let inference = infer_scope_types(self.db(), scope);
|
let inference = infer_scope_types(self.db(), scope);
|
||||||
let yield_type = inference.expression_type(elt.as_ref());
|
let yield_type = inference.expression_type(elt.as_ref());
|
||||||
|
|
@ -8021,9 +8024,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
|
|
||||||
self.infer_first_comprehension_iter(generators);
|
self.infer_first_comprehension_iter(generators);
|
||||||
|
|
||||||
let scope_id = self
|
let Some(scope_id) = self
|
||||||
.index
|
.index
|
||||||
.node_scope(NodeWithScopeRef::ListComprehension(listcomp));
|
.try_node_scope(NodeWithScopeRef::ListComprehension(listcomp))
|
||||||
|
else {
|
||||||
|
return Type::unknown();
|
||||||
|
};
|
||||||
let scope = scope_id.to_scope_id(self.db(), self.file());
|
let scope = scope_id.to_scope_id(self.db(), self.file());
|
||||||
let inference = infer_scope_types(self.db(), scope);
|
let inference = infer_scope_types(self.db(), scope);
|
||||||
let element_type = inference.expression_type(elt.as_ref());
|
let element_type = inference.expression_type(elt.as_ref());
|
||||||
|
|
@ -8046,9 +8052,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
|
|
||||||
self.infer_first_comprehension_iter(generators);
|
self.infer_first_comprehension_iter(generators);
|
||||||
|
|
||||||
let scope_id = self
|
let Some(scope_id) = self
|
||||||
.index
|
.index
|
||||||
.node_scope(NodeWithScopeRef::DictComprehension(dictcomp));
|
.try_node_scope(NodeWithScopeRef::DictComprehension(dictcomp))
|
||||||
|
else {
|
||||||
|
return Type::unknown();
|
||||||
|
};
|
||||||
let scope = scope_id.to_scope_id(self.db(), self.file());
|
let scope = scope_id.to_scope_id(self.db(), self.file());
|
||||||
let inference = infer_scope_types(self.db(), scope);
|
let inference = infer_scope_types(self.db(), scope);
|
||||||
let key_type = inference.expression_type(key.as_ref());
|
let key_type = inference.expression_type(key.as_ref());
|
||||||
|
|
@ -8071,9 +8080,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
|
|
||||||
self.infer_first_comprehension_iter(generators);
|
self.infer_first_comprehension_iter(generators);
|
||||||
|
|
||||||
let scope_id = self
|
let Some(scope_id) = self
|
||||||
.index
|
.index
|
||||||
.node_scope(NodeWithScopeRef::SetComprehension(setcomp));
|
.try_node_scope(NodeWithScopeRef::SetComprehension(setcomp))
|
||||||
|
else {
|
||||||
|
return Type::unknown();
|
||||||
|
};
|
||||||
let scope = scope_id.to_scope_id(self.db(), self.file());
|
let scope = scope_id.to_scope_id(self.db(), self.file());
|
||||||
let inference = infer_scope_types(self.db(), scope);
|
let inference = infer_scope_types(self.db(), scope);
|
||||||
let element_type = inference.expression_type(elt.as_ref());
|
let element_type = inference.expression_type(elt.as_ref());
|
||||||
|
|
@ -8165,14 +8177,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
builder.module(),
|
builder.module(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
builder.infer_standalone_expression(iter, tcx)
|
builder.infer_maybe_standalone_expression(iter, tcx)
|
||||||
}
|
}
|
||||||
.iterate(builder.db())
|
.iterate(builder.db())
|
||||||
.homogeneous_element_type(builder.db())
|
.homogeneous_element_type(builder.db())
|
||||||
});
|
});
|
||||||
|
|
||||||
for expr in ifs {
|
for expr in ifs {
|
||||||
self.infer_standalone_expression(expr, TypeContext::default());
|
self.infer_maybe_standalone_expression(expr, TypeContext::default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -8278,7 +8290,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
orelse,
|
orelse,
|
||||||
} = if_expression;
|
} = if_expression;
|
||||||
|
|
||||||
let test_ty = self.infer_standalone_expression(test, TypeContext::default());
|
let test_ty = self.infer_maybe_standalone_expression(test, TypeContext::default());
|
||||||
let body_ty = self.infer_expression(body, tcx);
|
let body_ty = self.infer_expression(body, tcx);
|
||||||
let orelse_ty = self.infer_expression(orelse, tcx);
|
let orelse_ty = self.infer_expression(orelse, tcx);
|
||||||
|
|
||||||
|
|
@ -10341,7 +10353,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
let ty = if index == values.len() - 1 {
|
let ty = if index == values.len() - 1 {
|
||||||
builder.infer_expression(value, TypeContext::default())
|
builder.infer_expression(value, TypeContext::default())
|
||||||
} else {
|
} else {
|
||||||
builder.infer_standalone_expression(value, TypeContext::default())
|
builder.infer_maybe_standalone_expression(value, TypeContext::default())
|
||||||
};
|
};
|
||||||
|
|
||||||
(ty, value.range())
|
(ty, value.range())
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,10 @@
|
||||||
//! argument types and return types. For each callable type in the union, the call expression's
|
//! argument types and return types. For each callable type in the union, the call expression's
|
||||||
//! arguments must match _at least one_ overload.
|
//! arguments must match _at least one_ overload.
|
||||||
|
|
||||||
use std::{collections::HashMap, slice::Iter};
|
use std::slice::Iter;
|
||||||
|
|
||||||
use itertools::{EitherOrBoth, Itertools};
|
use itertools::{EitherOrBoth, Itertools};
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use smallvec::{SmallVec, smallvec_inline};
|
use smallvec::{SmallVec, smallvec_inline};
|
||||||
|
|
||||||
use super::{DynamicType, Type, TypeVarVariance, definition_expression_type, semantic_index};
|
use super::{DynamicType, Type, TypeVarVariance, definition_expression_type, semantic_index};
|
||||||
|
|
@ -1470,7 +1471,7 @@ impl<'db> Signature<'db> {
|
||||||
let (self_parameters, other_parameters) = parameters.into_remaining();
|
let (self_parameters, other_parameters) = parameters.into_remaining();
|
||||||
|
|
||||||
// Collect all the keyword-only parameters and the unmatched standard parameters.
|
// Collect all the keyword-only parameters and the unmatched standard parameters.
|
||||||
let mut self_keywords = HashMap::new();
|
let mut self_keywords = FxHashMap::default();
|
||||||
|
|
||||||
// Type of the variadic keyword parameter in `self`.
|
// Type of the variadic keyword parameter in `self`.
|
||||||
//
|
//
|
||||||
|
|
@ -1483,7 +1484,7 @@ impl<'db> Signature<'db> {
|
||||||
match self_parameter.kind() {
|
match self_parameter.kind() {
|
||||||
ParameterKind::KeywordOnly { name, .. }
|
ParameterKind::KeywordOnly { name, .. }
|
||||||
| ParameterKind::PositionalOrKeyword { name, .. } => {
|
| ParameterKind::PositionalOrKeyword { name, .. } => {
|
||||||
self_keywords.insert(name.clone(), self_parameter);
|
self_keywords.insert(name.as_str(), self_parameter);
|
||||||
}
|
}
|
||||||
ParameterKind::KeywordVariadic { .. } => {
|
ParameterKind::KeywordVariadic { .. } => {
|
||||||
self_keyword_variadic = Some(self_parameter.annotated_type());
|
self_keyword_variadic = Some(self_parameter.annotated_type());
|
||||||
|
|
@ -1509,7 +1510,7 @@ impl<'db> Signature<'db> {
|
||||||
name: other_name,
|
name: other_name,
|
||||||
default_type: other_default,
|
default_type: other_default,
|
||||||
} => {
|
} => {
|
||||||
if let Some(self_parameter) = self_keywords.remove(other_name) {
|
if let Some(self_parameter) = self_keywords.remove(other_name.as_str()) {
|
||||||
match self_parameter.kind() {
|
match self_parameter.kind() {
|
||||||
ParameterKind::PositionalOrKeyword {
|
ParameterKind::PositionalOrKeyword {
|
||||||
default_type: self_default,
|
default_type: self_default,
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,14 @@ impl EnvVars {
|
||||||
/// when necessary, e.g. to watch for file system changes or a dedicated UI thread.
|
/// when necessary, e.g. to watch for file system changes or a dedicated UI thread.
|
||||||
pub const TY_MAX_PARALLELISM: &'static str = "TY_MAX_PARALLELISM";
|
pub const TY_MAX_PARALLELISM: &'static str = "TY_MAX_PARALLELISM";
|
||||||
|
|
||||||
|
/// Path to a `ty.toml` configuration file to use.
|
||||||
|
///
|
||||||
|
/// When set, ty will use this file for configuration instead of
|
||||||
|
/// discovering configuration files automatically.
|
||||||
|
///
|
||||||
|
/// Equivalent to the `--config-file` command-line argument.
|
||||||
|
pub const TY_CONFIG_FILE: &'static str = "TY_CONFIG_FILE";
|
||||||
|
|
||||||
/// Used to detect an activated virtual environment.
|
/// Used to detect an activated virtual environment.
|
||||||
pub const VIRTUAL_ENV: &'static str = "VIRTUAL_ENV";
|
pub const VIRTUAL_ENV: &'static str = "VIRTUAL_ENV";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -376,6 +376,10 @@ class IncrementalEditTest(LspTest):
|
||||||
"The after edit diagnostics should be initialized if the test ran at least once. Did you forget to call `run`?"
|
"The after edit diagnostics should be initialized if the test ran at least once. Did you forget to call `run`?"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
new_diagnostics = diff_diagnostics(
|
||||||
|
self.before_edit_diagnostics, self.after_edit_diagnostics
|
||||||
|
)
|
||||||
|
|
||||||
before_edit_count = sum(
|
before_edit_count = sum(
|
||||||
len(diagnostics) for _, diagnostics in self.before_edit_diagnostics
|
len(diagnostics) for _, diagnostics in self.before_edit_diagnostics
|
||||||
)
|
)
|
||||||
|
|
@ -384,7 +388,7 @@ class IncrementalEditTest(LspTest):
|
||||||
len(diagnostics) for _, diagnostics in self.after_edit_diagnostics
|
len(diagnostics) for _, diagnostics in self.after_edit_diagnostics
|
||||||
)
|
)
|
||||||
|
|
||||||
assert after_edit_count > before_edit_count, (
|
assert len(new_diagnostics) > 0, (
|
||||||
f"Expected more diagnostics after the change. "
|
f"Expected more diagnostics after the change. "
|
||||||
f"Initial: {before_edit_count}, After change: {after_edit_count}"
|
f"Initial: {before_edit_count}, After change: {after_edit_count}"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue