Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion crates/ty_python_semantic/resources/mdtest/annotations/self.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

```toml
[environment]
python-version = "3.11"
python-version = "3.13"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default values for type parameters have only been introduced recently (https://peps.python.org/pep-0696/)

```

`Self` is treated as if it were a `TypeVar` bound to the class it's being used on.
Expand Down Expand Up @@ -147,6 +147,23 @@ class Shape:
return self
```

## `Self` for classes with a default value for their generic parameter

This is a regression test for <https://github.com/astral-sh/ty/issues/1156>.

```py
from typing import Self

class Container[T = bytes]:
def __init__(self: Self, data: T | None = None) -> None:
self.data = data

reveal_type(Container()) # revealed: Container[bytes]
reveal_type(Container(1)) # revealed: Container[int]
reveal_type(Container("a")) # revealed: Container[str]
reveal_type(Container(b"a")) # revealed: Container[bytes]
```

## Invalid Usage

`Self` cannot be used in the signature of a function or variable.
Expand Down
4 changes: 1 addition & 3 deletions crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5708,9 +5708,7 @@ impl<'db> Type<'db> {
],
});
};
let instance = Type::ClassLiteral(class).to_instance(db).expect(
"nearest_enclosing_class must return type that can be instantiated",
);
Comment on lines -5711 to -5713
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would previously use the "default" specialization, and so we would end up with an upper bound of Container[bytes] for the example in the mdtest. With the change here, we instead use Container[Unknown] as the upper bound.

let instance = Type::instance(db, class.unknown_specialization(db));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wondered if class.top_materialization() would work here, but it looks like under the hood that uses default_specialization(), so it suffers from the same issue as our current logic on main. (It probably shouldn't use default_specialization()?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It probably shouldn't use default_specialization()

I think you're right — it should translate typevars into the bound/constraints in covariant position, and to Never (the implicit lower bound of every typevar) in contravariant position. (With the caveat that we can't really express the constraints of a constrained typevar as a single type — we'd need a "one-of" connective instead of union, and an "instance of this type but not any subtypes".)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let class_definition = class.definition(db);
let typevar = TypeVarInstance::new(
db,
Expand Down
Loading