Skip to content

Commit

Permalink
Merge branch 'main' into dcreager/alist-type
Browse files Browse the repository at this point in the history
* main:
  [red-knot] Switch to a handwritten parser for mdtest error assertions (#16422)
  [red-knot] Disallow more invalid type expressions (#16427)
  Bump version to Ruff 0.9.9 (#16434)
  Check `LinterSettings::preview` for version-related syntax errors (#16429)
  Avoid caching files with unsupported syntax errors (#16425)
  Prioritize "bug" label for changelog sections (#16433)
  [`flake8-copyright`] Add links to applicable options (`CPY001`) (#16421)
  Fix string-length limit in documentation for PYI054 (#16432)
  Show version-related syntax errors in the playground (#16419)
  Allow passing `ParseOptions` to inline tests (#16357)
  Bump version to 0.9.8 (#16414)
  [red-knot] Ignore surrounding whitespace when looking for `<!-- snapshot-diagnostics -->` directives in mdtests (#16380)
  Notify users for invalid client settings (#16361)
  Avoid indexing the project if `configurationPreference` is `editorOnly` (#16381)
  • Loading branch information
dcreager committed Feb 28, 2025
2 parents 29cdd6e + 0c7c001 commit 18397f3
Show file tree
Hide file tree
Showing 34 changed files with 1,247 additions and 204 deletions.
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,51 @@
# Changelog

## 0.9.9

### Preview features

- Fix caching of unsupported-syntax errors ([#16425](https://github.com/astral-sh/ruff/pull/16425))

### Bug fixes

- Only show unsupported-syntax errors in editors when preview mode is enabled ([#16429](https://github.com/astral-sh/ruff/pull/16429))

## 0.9.8

### Preview features

- Start detecting version-related syntax errors in the parser ([#16090](https://github.com/astral-sh/ruff/pull/16090))

### Rule changes

- \[`pylint`\] Mark fix unsafe (`PLW1507`) ([#16343](https://github.com/astral-sh/ruff/pull/16343))
- \[`pylint`\] Catch `case np.nan`/`case math.nan` in `match` statements (`PLW0177`) ([#16378](https://github.com/astral-sh/ruff/pull/16378))
- \[`ruff`\] Add more Pydantic models variants to the list of default copy semantics (`RUF012`) ([#16291](https://github.com/astral-sh/ruff/pull/16291))

### Server

- Avoid indexing the project if `configurationPreference` is `editorOnly` ([#16381](https://github.com/astral-sh/ruff/pull/16381))
- Avoid unnecessary info at non-trace server log level ([#16389](https://github.com/astral-sh/ruff/pull/16389))
- Expand `ruff.configuration` to allow inline config ([#16296](https://github.com/astral-sh/ruff/pull/16296))
- Notify users for invalid client settings ([#16361](https://github.com/astral-sh/ruff/pull/16361))

### Configuration

- Add `per-file-target-version` option ([#16257](https://github.com/astral-sh/ruff/pull/16257))

### Bug fixes

- \[`refurb`\] Do not consider docstring(s) (`FURB156`) ([#16391](https://github.com/astral-sh/ruff/pull/16391))
- \[`flake8-self`\] Ignore attribute accesses on instance-like variables (`SLF001`) ([#16149](https://github.com/astral-sh/ruff/pull/16149))
- \[`pylint`\] Fix false positives, add missing methods, and support positional-only parameters (`PLE0302`) ([#16263](https://github.com/astral-sh/ruff/pull/16263))
- \[`flake8-pyi`\] Mark `PYI030` fix unsafe when comments are deleted ([#16322](https://github.com/astral-sh/ruff/pull/16322))

### Documentation

- Fix example for `S611` ([#16316](https://github.com/astral-sh/ruff/pull/16316))
- Normalize inconsistent markdown headings in docstrings ([#16364](https://github.com/astral-sh/ruff/pull/16364))
- Document MSRV policy ([#16384](https://github.com/astral-sh/ruff/pull/16384))

## 0.9.7

### Preview features
Expand Down
9 changes: 6 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"

# For a specific version.
curl -LsSf https://astral.sh/ruff/0.9.7/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.9.7/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.9.9/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.9.9/install.ps1 | iex"
```

You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
Expand Down Expand Up @@ -183,7 +183,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.9.7
rev: v0.9.9
hooks:
# Run the linter.
- id: ruff
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Tests for invalid types in type expressions

## Invalid types are rejected

Many types are illegal in the context of a type expression:

```py
import typing
from knot_extensions import AlwaysTruthy, AlwaysFalsy
from typing_extensions import Literal, Never

def _(
a: type[int],
b: AlwaysTruthy,
c: AlwaysFalsy,
d: Literal[True],
e: Literal["bar"],
f: Literal[b"foo"],
g: tuple[int, str],
h: Never,
):
def foo(): ...
def invalid(
i: a, # error: [invalid-type-form] "Variable of type `type[int]` is not allowed in a type expression"
j: b, # error: [invalid-type-form]
k: c, # error: [invalid-type-form]
l: d, # error: [invalid-type-form]
m: e, # error: [invalid-type-form]
n: f, # error: [invalid-type-form]
o: g, # error: [invalid-type-form]
p: h, # error: [invalid-type-form]
q: typing, # error: [invalid-type-form]
r: foo, # error: [invalid-type-form]
):
reveal_type(i) # revealed: Unknown
reveal_type(j) # revealed: Unknown
reveal_type(k) # revealed: Unknown
reveal_type(l) # revealed: Unknown
reveal_type(m) # revealed: Unknown
reveal_type(n) # revealed: Unknown
reveal_type(o) # revealed: Unknown
reveal_type(p) # revealed: Unknown
reveal_type(q) # revealed: Unknown
reveal_type(r) # revealed: Unknown
```
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
# TODO: should understand the annotation
reveal_type(args) # revealed: tuple

reveal_type(Alias) # revealed: @Todo(Unsupported or invalid type in a type expression)
reveal_type(Alias) # revealed: @Todo(Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`)

def g() -> TypeGuard[int]: ...
def h() -> TypeIs[int]: ...
Expand All @@ -33,7 +33,7 @@ def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.

class Foo:
def method(self, x: Self):
reveal_type(x) # revealed: @Todo(Unsupported or invalid type in a type expression)
reveal_type(x) # revealed: @Todo(Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`)
```

## Inheritance
Expand Down
122 changes: 84 additions & 38 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2470,32 +2470,45 @@ impl<'db> Type<'db> {
match self {
// Special cases for `float` and `complex`
// https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex
Type::ClassLiteral(ClassLiteralType { class })
if class.is_known(db, KnownClass::Float) =>
{
Ok(UnionType::from_elements(
db,
[
KnownClass::Int.to_instance(db),
KnownClass::Float.to_instance(db),
],
))
}
Type::ClassLiteral(ClassLiteralType { class })
if class.is_known(db, KnownClass::Complex) =>
{
Ok(UnionType::from_elements(
db,
[
KnownClass::Int.to_instance(db),
KnownClass::Float.to_instance(db),
KnownClass::Complex.to_instance(db),
],
))
Type::ClassLiteral(ClassLiteralType { class }) => {
let ty = match class.known(db) {
Some(KnownClass::Complex) => UnionType::from_elements(
db,
[
KnownClass::Int.to_instance(db),
KnownClass::Float.to_instance(db),
KnownClass::Complex.to_instance(db),
],
),
Some(KnownClass::Float) => UnionType::from_elements(
db,
[
KnownClass::Int.to_instance(db),
KnownClass::Float.to_instance(db),
],
),
_ => Type::instance(*class),
};
Ok(ty)
}
// In a type expression, a bare `type` is interpreted as "instance of `type`", which is
// equivalent to `type[object]`.
Type::ClassLiteral(_) | Type::SubclassOf(_) => Ok(self.to_instance(db)),
Type::SubclassOf(_)
| Type::BooleanLiteral(_)
| Type::BytesLiteral(_)
| Type::AlwaysTruthy
| Type::AlwaysFalsy
| Type::SliceLiteral(_)
| Type::IntLiteral(_)
| Type::LiteralString
| Type::ModuleLiteral(_)
| Type::StringLiteral(_)
| Type::Tuple(_)
| Type::Callable(_)
| Type::Never
| Type::FunctionLiteral(_) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(*self)],
fallback_type: Type::unknown(),
}),

// We treat `typing.Type` exactly the same as `builtins.type`:
Type::KnownInstance(KnownInstanceType::Type) => Ok(KnownClass::Type.to_instance(db)),
Type::KnownInstance(KnownInstanceType::Tuple) => Ok(KnownClass::Tuple.to_instance(db)),
Expand Down Expand Up @@ -2556,7 +2569,6 @@ impl<'db> Type<'db> {
}
Type::KnownInstance(KnownInstanceType::LiteralString) => Ok(Type::LiteralString),
Type::KnownInstance(KnownInstanceType::Any) => Ok(Type::any()),
// TODO: Should emit a diagnostic
Type::KnownInstance(KnownInstanceType::Annotated) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::BareAnnotated],
fallback_type: Type::unknown(),
Expand All @@ -2580,9 +2592,13 @@ impl<'db> Type<'db> {
Type::KnownInstance(KnownInstanceType::Unknown) => Ok(Type::unknown()),
Type::KnownInstance(KnownInstanceType::AlwaysTruthy) => Ok(Type::AlwaysTruthy),
Type::KnownInstance(KnownInstanceType::AlwaysFalsy) => Ok(Type::AlwaysFalsy),
_ => Ok(todo_type!(
"Unsupported or invalid type in a type expression"
Type::KnownInstance(_) => Ok(todo_type!(
"Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`"
)),
Type::Instance(_) => Ok(todo_type!(
"Invalid or unsupported `Instance` in `Type::to_type_expression`"
)),
Type::Intersection(_) => Ok(todo_type!("Type::Intersection.in_type_expression")),
}
}

Expand Down Expand Up @@ -2815,7 +2831,7 @@ impl<'db> From<Type<'db>> for TypeAndQualifiers<'db> {
#[derive(Debug, PartialEq, Eq)]
pub struct InvalidTypeExpressionError<'db> {
fallback_type: Type<'db>,
invalid_expressions: smallvec::SmallVec<[InvalidTypeExpression; 1]>,
invalid_expressions: smallvec::SmallVec<[InvalidTypeExpression<'db>; 1]>,
}

impl<'db> InvalidTypeExpressionError<'db> {
Expand All @@ -2825,15 +2841,19 @@ impl<'db> InvalidTypeExpressionError<'db> {
invalid_expressions,
} = self;
for error in invalid_expressions {
context.report_lint(&INVALID_TYPE_FORM, node, format_args!("{}", error.reason()));
context.report_lint(
&INVALID_TYPE_FORM,
node,
format_args!("{}", error.reason(context.db())),
);
}
fallback_type
}
}

/// Enumeration of various types that are invalid in type-expression contexts
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum InvalidTypeExpression {
enum InvalidTypeExpression<'db> {
/// `x: Annotated` is invalid as an annotation
BareAnnotated,
/// `x: Literal` is invalid as an annotation
Expand All @@ -2842,16 +2862,42 @@ enum InvalidTypeExpression {
ClassVarInTypeExpression,
/// The `Final` type qualifier was used in a type expression
FinalInTypeExpression,
/// Some types are always invalid in type expressions
InvalidType(Type<'db>),
}

impl InvalidTypeExpression {
const fn reason(self) -> &'static str {
match self {
Self::BareAnnotated => "`Annotated` requires at least two arguments when used in an annotation or type expression",
Self::BareLiteral => "`Literal` requires at least one argument when used in a type expression",
Self::ClassVarInTypeExpression => "Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)",
Self::FinalInTypeExpression => "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)",
impl<'db> InvalidTypeExpression<'db> {
const fn reason(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
struct Display<'db> {
error: InvalidTypeExpression<'db>,
db: &'db dyn Db,
}

impl std::fmt::Display for Display<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.error {
InvalidTypeExpression::BareAnnotated => f.write_str(
"`Annotated` requires at least two arguments when used in an annotation or type expression"
),
InvalidTypeExpression::BareLiteral => f.write_str(
"`Literal` requires at least one argument when used in a type expression"
),
InvalidTypeExpression::ClassVarInTypeExpression => f.write_str(
"Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)"
),
InvalidTypeExpression::FinalInTypeExpression => f.write_str(
"Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)"
),
InvalidTypeExpression::InvalidType(ty) => write!(
f,
"Variable of type `{ty}` is not allowed in a type expression",
ty = ty.display(self.db)
),
}
}
}

Display { error: self, db }
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/red_knot_test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ salsa = { workspace = true }
smallvec = { workspace = true }
serde = { workspace = true }
toml = { workspace = true }
thiserror = { workspace = true }

[lints]
workspace = true
Loading

0 comments on commit 18397f3

Please sign in to comment.