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
10 changes: 5 additions & 5 deletions crates/ty_ide/src/goto_type_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -561,13 +561,13 @@ f(**kwargs<CURSOR>)

assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/types.pyi:941:11
--> stdlib/types.pyi:950:11
|
939 | if sys.version_info >= (3, 10):
940 | @final
941 | class NoneType:
948 | if sys.version_info >= (3, 10):
949 | @final
950 | class NoneType:
| ^^^^^^^^
942 | """The type of the None singleton."""
951 | """The type of the None singleton."""
|
info: Source
--> main.py:3:5
Expand Down
9 changes: 6 additions & 3 deletions crates/ty_python_semantic/resources/mdtest/call/replace.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ from datetime import time
t = time(12, 0, 0)
t = replace(t, minute=30)

reveal_type(t) # revealed: time
# TODO: this should be `time`, once we support specialization of generic protocols
reveal_type(t) # revealed: Unknown
Copy link
Contributor

Choose a reason for hiding this comment

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

python/typeshed#14786 changed the signature of copy.replace from

_SR = TypeVar("_SR", bound=_SupportsReplace)

def replace(obj: _SR, /, **changes: Any) -> _SR: ...

to

def replace(obj: _SupportsReplace[_RT_co], /, **changes: Any) -> _RT_co: ...

which our generics solver does not support yet 😢.

I don't think it's important enough to implement a workaround for, though.

Copy link
Member

Choose a reason for hiding this comment

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

Agreed

```

## The `__replace__` protocol
Expand Down Expand Up @@ -47,7 +48,8 @@ b = a.__replace__(x=3, y=4)
reveal_type(b) # revealed: Point

b = replace(a, x=3, y=4)
reveal_type(b) # revealed: Point
# TODO: this should be `Point`, once we support specialization of generic protocols
reveal_type(b) # revealed: Unknown
```

A call to `replace` does not require all keyword arguments:
Expand All @@ -57,7 +59,8 @@ c = a.__replace__(y=4)
reveal_type(c) # revealed: Point

d = replace(a, y=4)
reveal_type(d) # revealed: Point
# TODO: this should be `Point`, once we support specialization of generic protocols
reveal_type(d) # revealed: Unknown
```

Invalid calls to `__replace__` or `replace` will raise an error:
Expand Down
12 changes: 10 additions & 2 deletions crates/ty_python_semantic/src/types/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4403,8 +4403,8 @@ impl KnownClass {
KnownClassDisplay { db, class: self }
}

/// Lookup a [`KnownClass`] in typeshed and return a [`Type`]
/// representing all possible instances of the class.
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`] representing all possible instances of
/// the class. If this class is generic, this will use the default specialization.
///
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
pub(crate) fn to_instance(self, db: &dyn Db) -> Type<'_> {
Expand All @@ -4414,6 +4414,14 @@ impl KnownClass {
.unwrap_or_else(Type::unknown)
}

/// Similar to [`KnownClass::to_instance`], but returns the Unknown-specialization where each type
/// parameter is specialized to `Unknown`.
pub(crate) fn to_instance_unknown(self, db: &dyn Db) -> Type<'_> {
self.try_to_class_literal(db)
.map(|literal| Type::instance(db, literal.unknown_specialization(db)))
.unwrap_or_else(Type::unknown)
}

/// Lookup a generic [`KnownClass`] in typeshed and return a [`Type`]
/// representing a specialization of that class.
///
Expand Down
8 changes: 5 additions & 3 deletions crates/ty_python_semantic/src/types/infer/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1923,16 +1923,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
//
// If type arguments are supplied to `(Async)Iterable`, `(Async)Iterator`,
// `(Async)Generator` or `(Async)GeneratorType` in the return annotation,
// we should iterate over the `yield` expressions and `return` statements in the function
// to check that they are consistent with the type arguments provided.
// we should iterate over the `yield` expressions and `return` statements
// in the function to check that they are consistent with the type arguments
// provided. Once we do this, the `.to_instance_unknown` call below should
// be replaced with `.to_specialized_instance`.
let inferred_return = if function.is_async {
KnownClass::AsyncGeneratorType
} else {
KnownClass::GeneratorType
};

if !inferred_return
.to_instance(self.db())
.to_instance_unknown(self.db())
.is_assignable_to(self.db(), expected_ty)
{
report_invalid_generator_function_return_type(
Expand Down
2 changes: 1 addition & 1 deletion crates/ty_vendored/vendor/typeshed/source_commit.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
47dbbd6c914a5190d54bc5bd498d1e6633d97db2
91055c730ffcda6311654cf32d663858ece69bad
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ Unit tests are in test_collections.
import sys
from abc import abstractmethod
from types import MappingProxyType
from typing import ( # noqa: Y022,Y038,UP035
from typing import ( # noqa: Y022,Y038,UP035,Y057
AbstractSet as Set,
AsyncGenerator as AsyncGenerator,
AsyncIterable as AsyncIterable,
AsyncIterator as AsyncIterator,
Awaitable as Awaitable,
ByteString as ByteString,
Callable as Callable,
ClassVar,
Collection as Collection,
Expand Down Expand Up @@ -64,12 +65,8 @@ __all__ = [
"ValuesView",
"Sequence",
"MutableSequence",
"ByteString",
]
if sys.version_info < (3, 14):
from typing import ByteString as ByteString # noqa: Y057,UP035

__all__ += ["ByteString"]

if sys.version_info >= (3, 12):
__all__ += ["Buffer"]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def __import__(
name: str,
globals: Mapping[str, object] | None = None,
locals: Mapping[str, object] | None = None,
fromlist: Sequence[str] = (),
fromlist: Sequence[str] | None = (),
level: int = 0,
) -> ModuleType:
"""Import a module.
Expand Down
40 changes: 20 additions & 20 deletions crates/ty_vendored/vendor/typeshed/stdlib/_tkinter.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -57,34 +57,34 @@ _TkinterTraceFunc: TypeAlias = Callable[[tuple[str, ...]], object]
@final
class TkappType:
# Please keep in sync with tkinter.Tk
def adderrorinfo(self, msg, /): ...
def adderrorinfo(self, msg: str, /): ...
def call(self, command: Any, /, *args: Any) -> Any: ...
def createcommand(self, name, func, /): ...
def createcommand(self, name: str, func, /): ...
if sys.platform != "win32":
def createfilehandler(self, file, mask, func, /): ...
def deletefilehandler(self, file, /): ...
def createfilehandler(self, file, mask: int, func, /): ...
def deletefilehandler(self, file, /) -> None: ...

def createtimerhandler(self, milliseconds, func, /): ...
def deletecommand(self, name, /): ...
def createtimerhandler(self, milliseconds: int, func, /): ...
def deletecommand(self, name: str, /): ...
def dooneevent(self, flags: int = 0, /): ...
def eval(self, script: str, /) -> str: ...
def evalfile(self, fileName, /): ...
def exprboolean(self, s, /): ...
def exprdouble(self, s, /): ...
def exprlong(self, s, /): ...
def exprstring(self, s, /): ...
def getboolean(self, arg, /): ...
def getdouble(self, arg, /): ...
def getint(self, arg, /): ...
def evalfile(self, fileName: str, /): ...
def exprboolean(self, s: str, /): ...
def exprdouble(self, s: str, /): ...
def exprlong(self, s: str, /): ...
def exprstring(self, s: str, /): ...
def getboolean(self, arg, /) -> bool: ...
def getdouble(self, arg, /) -> float: ...
def getint(self, arg, /) -> int: ...
def getvar(self, *args, **kwargs): ...
def globalgetvar(self, *args, **kwargs): ...
def globalsetvar(self, *args, **kwargs): ...
def globalunsetvar(self, *args, **kwargs): ...
def interpaddr(self) -> int: ...
def loadtk(self) -> None: ...
def mainloop(self, threshold: int = 0, /): ...
def quit(self): ...
def record(self, script, /): ...
def mainloop(self, threshold: int = 0, /) -> None: ...
def quit(self) -> None: ...
def record(self, script: str, /): ...
def setvar(self, *ags, **kwargs): ...
if sys.version_info < (3, 11):
@deprecated("Deprecated since Python 3.9; removed in Python 3.11. Use `splitlist()` instead.")
Expand All @@ -93,7 +93,7 @@ class TkappType:
def splitlist(self, arg, /): ...
def unsetvar(self, *args, **kwargs): ...
def wantobjects(self, *args, **kwargs): ...
def willdispatch(self): ...
def willdispatch(self) -> None: ...
if sys.version_info >= (3, 12):
def gettrace(self, /) -> _TkinterTraceFunc | None:
"""Get the tracing function."""
Expand Down Expand Up @@ -164,10 +164,10 @@ else:
if not None, then pass -use to wish
"""

def getbusywaitinterval():
def getbusywaitinterval() -> int:
"""Return the current busy-wait interval between successive calls to Tcl_DoOneEvent in a threaded Python interpreter."""

def setbusywaitinterval(new_val, /):
def setbusywaitinterval(new_val: int, /) -> None:
"""Set the busy-wait interval in milliseconds between successive calls to Tcl_DoOneEvent in a threaded Python interpreter.

It should be set to a divisor of the maximum time between frames in an animation.
Expand Down
10 changes: 10 additions & 0 deletions crates/ty_vendored/vendor/typeshed/stdlib/asyncio/tools.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Tools to analyze tasks running in asyncio programs."""

import sys
from collections.abc import Iterable
from enum import Enum
from typing import NamedTuple, SupportsIndex, type_check_only
Expand Down Expand Up @@ -48,6 +49,15 @@ def build_async_tree(result: Iterable[_AwaitedInfo], task_emoji: str = "(T)", co
"""

def build_task_table(result: Iterable[_AwaitedInfo]) -> list[list[int | str]]: ...

if sys.version_info >= (3, 14):
def exit_with_permission_help_text() -> None:
"""
Prints a message pointing to platform-specific permission help text and exits the program.
This function is called when a PermissionError is encountered while trying
to attach to a process.
"""

def display_awaited_by_tasks_table(pid: SupportsIndex) -> None:
"""Build and print a table of all pending tasks under `pid`."""

Expand Down
11 changes: 9 additions & 2 deletions crates/ty_vendored/vendor/typeshed/stdlib/builtins.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -4153,7 +4153,14 @@ def open(
opener: _Opener | None = None,
) -> IO[Any]: ...
def ord(c: str | bytes | bytearray, /) -> int:
"""Return the Unicode code point for a one-character string."""
"""Return the ordinal value of a character.

If the argument is a one-character string, return the Unicode code
point of that character.

If the argument is a bytes or bytearray object of length 1, return its
single byte value.
"""

@type_check_only
class _SupportsWriteAndFlush(SupportsWrite[_T_contra], SupportsFlush, Protocol[_T_contra]): ...
Expand Down Expand Up @@ -4451,7 +4458,7 @@ def __import__(
name: str,
globals: Mapping[str, object] | None = None,
locals: Mapping[str, object] | None = None,
fromlist: Sequence[str] = (),
fromlist: Sequence[str] | None = (),
level: int = 0,
) -> types.ModuleType:
"""Import a module.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,60 +71,74 @@ if sys.version_info >= (3, 13): # needed to satisfy pyright checks for Python <
def empty(self) -> bool: ...
def full(self) -> bool: ...
def qsize(self) -> int: ...
def put(
self,
obj: object,
timeout: SupportsIndex | None = None,
*,
unbounditems: _AnyUnbound | None = None,
_delay: float = 0.01,
) -> None:
"""Add the object to the queue.

This blocks while the queue is full.

For most objects, the object received through Queue.get() will
be a new one, equivalent to the original and not sharing any
actual underlying data. The notable exceptions include
cross-interpreter types (like Queue) and memoryview, where the
underlying data is actually shared. Furthermore, some types
can be sent through a queue more efficiently than others. This
group includes various immutable types like int, str, bytes, and
tuple (if the items are likewise efficiently shareable). See interpreters.is_shareable().

"unbounditems" controls the behavior of Queue.get() for the given
object if the current interpreter (calling put()) is later
destroyed.

If "unbounditems" is None (the default) then it uses the
queue's default, set with create_queue(),
which is usually UNBOUND.

If "unbounditems" is UNBOUND_ERROR then get() will raise an
ItemInterpreterDestroyed exception if the original interpreter
has been destroyed. This does not otherwise affect the queue;
the next call to put() will work like normal, returning the next
item in the queue.

If "unbounditems" is UNBOUND_REMOVE then the item will be removed
from the queue as soon as the original interpreter is destroyed.
Be aware that this will introduce an imbalance between put()
and get() calls.

If "unbounditems" is UNBOUND then it is returned by get() in place
of the unbound item.
"""
if sys.version_info >= (3, 14):
def put(
self,
obj: object,
block: bool = True,
timeout: SupportsIndex | None = None,
*,
unbounditems: _AnyUnbound | None = None,
_delay: float = 0.01,
) -> None:
"""Add the object to the queue.

If "block" is true, this blocks while the queue is full.

For most objects, the object received through Queue.get() will
be a new one, equivalent to the original and not sharing any
actual underlying data. The notable exceptions include
cross-interpreter types (like Queue) and memoryview, where the
underlying data is actually shared. Furthermore, some types
can be sent through a queue more efficiently than others. This
group includes various immutable types like int, str, bytes, and
tuple (if the items are likewise efficiently shareable). See interpreters.is_shareable().

"unbounditems" controls the behavior of Queue.get() for the given
object if the current interpreter (calling put()) is later
destroyed.

If "unbounditems" is None (the default) then it uses the
queue's default, set with create_queue(),
which is usually UNBOUND.

If "unbounditems" is UNBOUND_ERROR then get() will raise an
ItemInterpreterDestroyed exception if the original interpreter
has been destroyed. This does not otherwise affect the queue;
the next call to put() will work like normal, returning the next
item in the queue.

If "unbounditems" is UNBOUND_REMOVE then the item will be removed
from the queue as soon as the original interpreter is destroyed.
Be aware that this will introduce an imbalance between put()
and get() calls.

If "unbounditems" is UNBOUND then it is returned by get() in place
of the unbound item.
"""
else:
def put(
self,
obj: object,
timeout: SupportsIndex | None = None,
*,
unbounditems: _AnyUnbound | None = None,
_delay: float = 0.01,
) -> None: ...

def put_nowait(self, obj: object, *, unbounditems: _AnyUnbound | None = None) -> None: ...
def get(self, timeout: SupportsIndex | None = None, *, _delay: float = 0.01) -> object:
"""Return the next object from the queue.

This blocks while the queue is empty.

If the next item's original interpreter has been destroyed
then the "next object" is determined by the value of the
"unbounditems" argument to put().
"""
if sys.version_info >= (3, 14):
def get(self, block: bool = True, timeout: SupportsIndex | None = None, *, _delay: float = 0.01) -> object:
"""Return the next object from the queue.

If "block" is true, this blocks while the queue is empty.

If the next item's original interpreter has been destroyed
then the "next object" is determined by the value of the
"unbounditems" argument to put().
"""
else:
def get(self, timeout: SupportsIndex | None = None, *, _delay: float = 0.01) -> object: ...

def get_nowait(self) -> object:
"""Return the next object from the channel.
Expand Down
6 changes: 3 additions & 3 deletions crates/ty_vendored/vendor/typeshed/stdlib/configparser.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -429,9 +429,9 @@ class RawConfigParser(_Parser):
) -> None: ...

def __len__(self) -> int: ...
def __getitem__(self, key: str) -> SectionProxy: ...
def __setitem__(self, key: str, value: _Section) -> None: ...
def __delitem__(self, key: str) -> None: ...
def __getitem__(self, key: _SectionName) -> SectionProxy: ...
def __setitem__(self, key: _SectionName, value: _Section) -> None: ...
def __delitem__(self, key: _SectionName) -> None: ...
def __iter__(self) -> Iterator[str]: ...
def __contains__(self, key: object) -> bool: ...
def defaults(self) -> _Section: ...
Expand Down
Loading
Loading