mirror of https://github.com/astral-sh/ruff
[ty] Enable even more goto-definition on inlay hints
This commit is contained in:
parent
ff0ed4e752
commit
fa2e319666
|
|
@ -6165,11 +6165,28 @@ mod tests {
|
|||
|
||||
test.with_extra_file("foo.py", "'''Foo module'''");
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
assert_snapshot!(test.inlay_hints(), @r#"
|
||||
import foo
|
||||
|
||||
a[: <module 'foo'>] = foo
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/types.pyi:423:7
|
||||
|
|
||||
422 | @disjoint_base
|
||||
423 | class ModuleType:
|
||||
| ^^^^^^^^^^
|
||||
424 | """Create a module object.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:6
|
||||
|
|
||||
2 | import foo
|
||||
3 |
|
||||
4 | a[: <module 'foo'>] = foo
|
||||
| ^^^^^^
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> foo.py:1:1
|
||||
|
|
||||
|
|
@ -6177,14 +6194,14 @@ mod tests {
|
|||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:5
|
||||
--> main2.py:4:14
|
||||
|
|
||||
2 | import foo
|
||||
3 |
|
||||
4 | a[: <module 'foo'>] = foo
|
||||
| ^^^^^^^^^^^^^^
|
||||
| ^^^
|
||||
|
|
||||
");
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -6200,9 +6217,508 @@ mod tests {
|
|||
from typing import Literal
|
||||
|
||||
a[: <special form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/typing.pyi:351:1
|
||||
|
|
||||
349 | Final: _SpecialForm
|
||||
350 |
|
||||
351 | Literal: _SpecialForm
|
||||
| ^^^^^^^
|
||||
352 | TypedDict: _SpecialForm
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:20
|
||||
|
|
||||
2 | from typing import Literal
|
||||
3 |
|
||||
4 | a[: <special form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||
| ^^^^^^^
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:915:7
|
||||
|
|
||||
914 | @disjoint_base
|
||||
915 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
916 | """str(object='') -> str
|
||||
917 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:28
|
||||
|
|
||||
2 | from typing import Literal
|
||||
3 |
|
||||
4 | a[: <special form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||
| ^^^
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:915:7
|
||||
|
|
||||
914 | @disjoint_base
|
||||
915 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
916 | """str(object='') -> str
|
||||
917 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:33
|
||||
|
|
||||
2 | from typing import Literal
|
||||
3 |
|
||||
4 | a[: <special form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||
| ^^^
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:915:7
|
||||
|
|
||||
914 | @disjoint_base
|
||||
915 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
916 | """str(object='') -> str
|
||||
917 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:38
|
||||
|
|
||||
2 | from typing import Literal
|
||||
3 |
|
||||
4 | a[: <special form 'Literal["a", "b", "c"]'>] = Literal['a', 'b', 'c']
|
||||
| ^^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrapper_descriptor_inlay_hint() {
|
||||
let mut test = inlay_hint_test(
|
||||
"
|
||||
from types import FunctionType
|
||||
|
||||
a = FunctionType.__get__",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r#"
|
||||
from types import FunctionType
|
||||
|
||||
a[: <wrapper-descriptor '__get__' of 'function' objects>] = FunctionType.__get__
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/types.pyi:670:7
|
||||
|
|
||||
669 | @final
|
||||
670 | class WrapperDescriptorType:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
671 | @property
|
||||
672 | def __name__(self) -> str: ...
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:6
|
||||
|
|
||||
2 | from types import FunctionType
|
||||
3 |
|
||||
4 | a[: <wrapper-descriptor '__get__' of 'function' objects>] = FunctionType.__get__
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/types.pyi:77:7
|
||||
|
|
||||
75 | # Make sure this class definition stays roughly in line with `builtins.function`
|
||||
76 | @final
|
||||
77 | class FunctionType:
|
||||
| ^^^^^^^^^^^^
|
||||
78 | """Create a function object.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:39
|
||||
|
|
||||
2 | from types import FunctionType
|
||||
3 |
|
||||
4 | a[: <wrapper-descriptor '__get__' of 'function' objects>] = FunctionType.__get__
|
||||
| ^^^^^^^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_method_wrapper_inlay_hint() {
|
||||
let mut test = inlay_hint_test(
|
||||
"
|
||||
def f(): ...
|
||||
|
||||
a = f.__call__",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r#"
|
||||
def f(): ...
|
||||
|
||||
a[: <method-wrapper '__call__' of function 'f'>] = f.__call__
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/types.pyi:684:7
|
||||
|
|
||||
683 | @final
|
||||
684 | class MethodWrapperType:
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
685 | @property
|
||||
686 | def __self__(self) -> object: ...
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:6
|
||||
|
|
||||
2 | def f(): ...
|
||||
3 |
|
||||
4 | a[: <method-wrapper '__call__' of function 'f'>] = f.__call__
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/types.pyi:134:9
|
||||
|
|
||||
132 | ) -> Self: ...
|
||||
133 |
|
||||
134 | def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
||||
| ^^^^^^^^
|
||||
135 | """Call self as a function."""
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:22
|
||||
|
|
||||
2 | def f(): ...
|
||||
3 |
|
||||
4 | a[: <method-wrapper '__call__' of function 'f'>] = f.__call__
|
||||
| ^^^^^^^^
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/types.pyi:77:7
|
||||
|
|
||||
75 | # Make sure this class definition stays roughly in line with `builtins.function`
|
||||
76 | @final
|
||||
77 | class FunctionType:
|
||||
| ^^^^^^^^^^^^
|
||||
78 | """Create a function object.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:35
|
||||
|
|
||||
2 | def f(): ...
|
||||
3 |
|
||||
4 | a[: <method-wrapper '__call__' of function 'f'>] = f.__call__
|
||||
| ^^^^^^^^
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:2:5
|
||||
|
|
||||
2 | def f(): ...
|
||||
| ^
|
||||
3 |
|
||||
4 | a = f.__call__
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:45
|
||||
|
|
||||
2 | def f(): ...
|
||||
3 |
|
||||
4 | a[: <method-wrapper '__call__' of function 'f'>] = f.__call__
|
||||
| ^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_newtype_inlay_hint() {
|
||||
let mut test = inlay_hint_test(
|
||||
"
|
||||
from typing import NewType
|
||||
|
||||
N = NewType('N', str)
|
||||
|
||||
Y = N",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r#"
|
||||
from typing import NewType
|
||||
|
||||
N[: <NewType pseudo-class 'N'>] = NewType([name=]'N', [tp=]str)
|
||||
|
||||
Y[: <NewType pseudo-class 'N'>] = N
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/typing.pyi:615:11
|
||||
|
|
||||
613 | TypeGuard: _SpecialForm
|
||||
614 |
|
||||
615 | class NewType:
|
||||
| ^^^^^^^
|
||||
616 | """NewType creates simple unique types with almost zero runtime overhead.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:6
|
||||
|
|
||||
2 | from typing import NewType
|
||||
3 |
|
||||
4 | N[: <NewType pseudo-class 'N'>] = NewType([name=]'N', [tp=]str)
|
||||
| ^^^^^^^
|
||||
5 |
|
||||
6 | Y[: <NewType pseudo-class 'N'>] = N
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | from typing import NewType
|
||||
3 |
|
||||
4 | N = NewType('N', str)
|
||||
| ^
|
||||
5 |
|
||||
6 | Y = N
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:28
|
||||
|
|
||||
2 | from typing import NewType
|
||||
3 |
|
||||
4 | N[: <NewType pseudo-class 'N'>] = NewType([name=]'N', [tp=]str)
|
||||
| ^
|
||||
5 |
|
||||
6 | Y[: <NewType pseudo-class 'N'>] = N
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/typing.pyi:637:28
|
||||
|
|
||||
635 | """
|
||||
636 |
|
||||
637 | def __init__(self, name: str, tp: Any) -> None: ... # AnnotationForm
|
||||
| ^^^^
|
||||
638 | if sys.version_info >= (3, 11):
|
||||
639 | @staticmethod
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:44
|
||||
|
|
||||
2 | from typing import NewType
|
||||
3 |
|
||||
4 | N[: <NewType pseudo-class 'N'>] = NewType([name=]'N', [tp=]str)
|
||||
| ^^^^
|
||||
5 |
|
||||
6 | Y[: <NewType pseudo-class 'N'>] = N
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/typing.pyi:637:39
|
||||
|
|
||||
635 | """
|
||||
636 |
|
||||
637 | def __init__(self, name: str, tp: Any) -> None: ... # AnnotationForm
|
||||
| ^^
|
||||
638 | if sys.version_info >= (3, 11):
|
||||
639 | @staticmethod
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:56
|
||||
|
|
||||
2 | from typing import NewType
|
||||
3 |
|
||||
4 | N[: <NewType pseudo-class 'N'>] = NewType([name=]'N', [tp=]str)
|
||||
| ^^
|
||||
5 |
|
||||
6 | Y[: <NewType pseudo-class 'N'>] = N
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/typing.pyi:615:11
|
||||
|
|
||||
613 | TypeGuard: _SpecialForm
|
||||
614 |
|
||||
615 | class NewType:
|
||||
| ^^^^^^^
|
||||
616 | """NewType creates simple unique types with almost zero runtime overhead.
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:6:6
|
||||
|
|
||||
4 | N[: <NewType pseudo-class 'N'>] = NewType([name=]'N', [tp=]str)
|
||||
5 |
|
||||
6 | Y[: <NewType pseudo-class 'N'>] = N
|
||||
| ^^^^^^^
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | from typing import NewType
|
||||
3 |
|
||||
4 | N = NewType('N', str)
|
||||
| ^
|
||||
5 |
|
||||
6 | Y = N
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:6:28
|
||||
|
|
||||
4 | N[: <NewType pseudo-class 'N'>] = NewType([name=]'N', [tp=]str)
|
||||
5 |
|
||||
6 | Y[: <NewType pseudo-class 'N'>] = N
|
||||
| ^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_meta_typevar_inlay_hint() {
|
||||
let mut test = inlay_hint_test(
|
||||
"
|
||||
def f[T](x: type[T]):
|
||||
y = x",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r#"
|
||||
def f[T](x: type[T]):
|
||||
y[: type[T@f]] = x
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:247:7
|
||||
|
|
||||
246 | @disjoint_base
|
||||
247 | class type:
|
||||
| ^^^^
|
||||
248 | """type(object) -> the object's type
|
||||
249 | type(name, bases, dict, **kwds) -> a new type
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:3:9
|
||||
|
|
||||
2 | def f[T](x: type[T]):
|
||||
3 | y[: type[T@f]] = x
|
||||
| ^^^^
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:2:7
|
||||
|
|
||||
2 | def f[T](x: type[T]):
|
||||
| ^
|
||||
3 | y = x
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:3:14
|
||||
|
|
||||
2 | def f[T](x: type[T]):
|
||||
3 | y[: type[T@f]] = x
|
||||
| ^^^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-edit]: File after edits
|
||||
info: Source
|
||||
|
||||
def f[T](x: type[T]):
|
||||
y: type[T@f] = x
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_subscripted_protocol_inlay_hint() {
|
||||
let mut test = inlay_hint_test(
|
||||
"
|
||||
from typing import Protocol, TypeVar
|
||||
T = TypeVar('T')
|
||||
Strange = Protocol[T]",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
from typing import Protocol, TypeVar
|
||||
T[: typing.TypeVar] = TypeVar([name=]'T')
|
||||
Strange[: <special form 'typing.Protocol[T]'>] = Protocol[T]
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:3:1
|
||||
|
|
||||
2 | from typing import Protocol, TypeVar
|
||||
3 | T = TypeVar('T')
|
||||
| ^
|
||||
4 | Strange = Protocol[T]
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:3:5
|
||||
|
|
||||
2 | from typing import Protocol, TypeVar
|
||||
3 | T[: typing.TypeVar] = TypeVar([name=]'T')
|
||||
| ^^^^^^^^^^^^^^
|
||||
4 | Strange[: <special form 'typing.Protocol[T]'>] = Protocol[T]
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/typing.pyi:276:13
|
||||
|
|
||||
274 | def __new__(
|
||||
275 | cls,
|
||||
276 | name: str,
|
||||
| ^^^^
|
||||
277 | *constraints: Any, # AnnotationForm
|
||||
278 | bound: Any | None = None, # AnnotationForm
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:3:32
|
||||
|
|
||||
2 | from typing import Protocol, TypeVar
|
||||
3 | T[: typing.TypeVar] = TypeVar([name=]'T')
|
||||
| ^^^^
|
||||
4 | Strange[: <special form 'typing.Protocol[T]'>] = Protocol[T]
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/typing.pyi:341:1
|
||||
|
|
||||
340 | Union: _SpecialForm
|
||||
341 | Protocol: _SpecialForm
|
||||
| ^^^^^^^^
|
||||
342 | Callable: _SpecialForm
|
||||
343 | Type: _SpecialForm
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:26
|
||||
|
|
||||
2 | from typing import Protocol, TypeVar
|
||||
3 | T[: typing.TypeVar] = TypeVar([name=]'T')
|
||||
4 | Strange[: <special form 'typing.Protocol[T]'>] = Protocol[T]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> main.py:3:1
|
||||
|
|
||||
2 | from typing import Protocol, TypeVar
|
||||
3 | T = TypeVar('T')
|
||||
| ^
|
||||
4 | Strange = Protocol[T]
|
||||
|
|
||||
info: Source
|
||||
--> main2.py:4:42
|
||||
|
|
||||
2 | from typing import Protocol, TypeVar
|
||||
3 | T[: typing.TypeVar] = TypeVar([name=]'T')
|
||||
4 | Strange[: <special form 'typing.Protocol[T]'>] = Protocol[T]
|
||||
| ^
|
||||
|
|
||||
|
||||
---------------------------------------------
|
||||
info[inlay-hint-edit]: File after edits
|
||||
info: Source
|
||||
|
||||
from typing import Protocol, TypeVar
|
||||
T: typing.TypeVar = TypeVar('T')
|
||||
Strange = Protocol[T]
|
||||
");
|
||||
}
|
||||
|
||||
struct InlayHintLocationDiagnostic {
|
||||
source: FileRange,
|
||||
target: FileRange,
|
||||
|
|
|
|||
|
|
@ -2162,8 +2162,8 @@ Some attributes are special-cased, however:
|
|||
import types
|
||||
from ty_extensions import static_assert, TypeOf, is_subtype_of
|
||||
|
||||
reveal_type(f.__get__) # revealed: <method-wrapper `__get__` of `f`>
|
||||
reveal_type(f.__call__) # revealed: <method-wrapper `__call__` of `f`>
|
||||
reveal_type(f.__get__) # revealed: <method-wrapper '__get__' of function 'f'>
|
||||
reveal_type(f.__call__) # revealed: <method-wrapper '__call__' of function 'f'>
|
||||
static_assert(is_subtype_of(TypeOf[f.__get__], types.MethodWrapperType))
|
||||
static_assert(is_subtype_of(TypeOf[f.__call__], types.MethodWrapperType))
|
||||
```
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ from inspect import getattr_static
|
|||
|
||||
reveal_type(getattr_static(C, "f")) # revealed: def f(self, x: int) -> str
|
||||
|
||||
reveal_type(getattr_static(C, "f").__get__) # revealed: <method-wrapper `__get__` of `f`>
|
||||
# revealed: <method-wrapper '__get__' of function 'f'>
|
||||
reveal_type(getattr_static(C, "f").__get__)
|
||||
|
||||
reveal_type(getattr_static(C, "f").__get__(None, C)) # revealed: def f(self, x: int) -> str
|
||||
reveal_type(getattr_static(C, "f").__get__(C(), C)) # revealed: bound method C.f(x: int) -> str
|
||||
|
|
@ -258,7 +259,7 @@ class C:
|
|||
|
||||
method_wrapper = getattr_static(C, "f").__get__
|
||||
|
||||
reveal_type(method_wrapper) # revealed: <method-wrapper `__get__` of `f`>
|
||||
reveal_type(method_wrapper) # revealed: <method-wrapper '__get__' of function 'f'>
|
||||
|
||||
# All of these are fine:
|
||||
method_wrapper(C(), C)
|
||||
|
|
@ -414,7 +415,8 @@ class C:
|
|||
def f(cls): ...
|
||||
|
||||
reveal_type(getattr_static(C, "f")) # revealed: def f(cls) -> Unknown
|
||||
reveal_type(getattr_static(C, "f").__get__) # revealed: <method-wrapper `__get__` of `f`>
|
||||
# revealed: <method-wrapper '__get__' of function 'f'>
|
||||
reveal_type(getattr_static(C, "f").__get__)
|
||||
```
|
||||
|
||||
But we correctly model how the `classmethod` descriptor works:
|
||||
|
|
@ -632,7 +634,7 @@ class MyClass:
|
|||
|
||||
static_assert(is_assignable_to(types.FunctionType, Callable))
|
||||
|
||||
# revealed: <wrapper-descriptor `__get__` of `function` objects>
|
||||
# revealed: <wrapper-descriptor '__get__' of 'function' objects>
|
||||
reveal_type(types.FunctionType.__get__)
|
||||
static_assert(is_assignable_to(TypeOf[types.FunctionType.__get__], Callable))
|
||||
|
||||
|
|
@ -640,7 +642,7 @@ static_assert(is_assignable_to(TypeOf[types.FunctionType.__get__], Callable))
|
|||
reveal_type(f)
|
||||
static_assert(is_assignable_to(TypeOf[f], Callable))
|
||||
|
||||
# revealed: <method-wrapper `__get__` of `f`>
|
||||
# revealed: <method-wrapper '__get__' of function 'f'>
|
||||
reveal_type(f.__get__)
|
||||
static_assert(is_assignable_to(TypeOf[f.__get__], Callable))
|
||||
|
||||
|
|
@ -648,11 +650,11 @@ static_assert(is_assignable_to(TypeOf[f.__get__], Callable))
|
|||
reveal_type(types.FunctionType.__call__)
|
||||
static_assert(is_assignable_to(TypeOf[types.FunctionType.__call__], Callable))
|
||||
|
||||
# revealed: <method-wrapper `__call__` of `f`>
|
||||
# revealed: <method-wrapper '__call__' of function 'f'>
|
||||
reveal_type(f.__call__)
|
||||
static_assert(is_assignable_to(TypeOf[f.__call__], Callable))
|
||||
|
||||
# revealed: <wrapper-descriptor `__get__` of `property` objects>
|
||||
# revealed: <wrapper-descriptor '__get__' of 'property' objects>
|
||||
reveal_type(property.__get__)
|
||||
static_assert(is_assignable_to(TypeOf[property.__get__], Callable))
|
||||
|
||||
|
|
@ -661,15 +663,15 @@ reveal_type(MyClass.my_property)
|
|||
static_assert(is_assignable_to(TypeOf[property], Callable))
|
||||
static_assert(not is_assignable_to(TypeOf[MyClass.my_property], Callable))
|
||||
|
||||
# revealed: <method-wrapper `__get__` of `property` object>
|
||||
# revealed: <method-wrapper '__get__' of property 'my_property'>
|
||||
reveal_type(MyClass.my_property.__get__)
|
||||
static_assert(is_assignable_to(TypeOf[MyClass.my_property.__get__], Callable))
|
||||
|
||||
# revealed: <wrapper-descriptor `__set__` of `property` objects>
|
||||
# revealed: <wrapper-descriptor '__set__' of 'property' objects>
|
||||
reveal_type(property.__set__)
|
||||
static_assert(is_assignable_to(TypeOf[property.__set__], Callable))
|
||||
|
||||
# revealed: <method-wrapper `__set__` of `property` object>
|
||||
# revealed: <method-wrapper '__set__' of property 'my_property'>
|
||||
reveal_type(MyClass.my_property.__set__)
|
||||
static_assert(is_assignable_to(TypeOf[MyClass.my_property.__set__], Callable))
|
||||
|
||||
|
|
@ -677,7 +679,7 @@ static_assert(is_assignable_to(TypeOf[MyClass.my_property.__set__], Callable))
|
|||
reveal_type(str.startswith)
|
||||
static_assert(is_assignable_to(TypeOf[str.startswith], Callable))
|
||||
|
||||
# revealed: <method-wrapper `startswith` of `str` object>
|
||||
# revealed: <method-wrapper 'startswith' of string 'foo'>
|
||||
reveal_type("foo".startswith)
|
||||
static_assert(is_assignable_to(TypeOf["foo".startswith], Callable))
|
||||
|
||||
|
|
|
|||
|
|
@ -596,14 +596,14 @@ def f(x: object) -> str:
|
|||
return "a"
|
||||
|
||||
reveal_type(f) # revealed: def f(x: object) -> str
|
||||
reveal_type(f.__get__) # revealed: <method-wrapper `__get__` of `f`>
|
||||
reveal_type(f.__get__) # revealed: <method-wrapper '__get__' of function 'f'>
|
||||
static_assert(is_subtype_of(TypeOf[f.__get__], types.MethodWrapperType))
|
||||
reveal_type(f.__get__(None, type(f))) # revealed: def f(x: object) -> str
|
||||
reveal_type(f.__get__(None, type(f))(1)) # revealed: str
|
||||
|
||||
wrapper_descriptor = getattr_static(f, "__get__")
|
||||
|
||||
reveal_type(wrapper_descriptor) # revealed: <wrapper-descriptor `__get__` of `function` objects>
|
||||
reveal_type(wrapper_descriptor) # revealed: <wrapper-descriptor '__get__' of 'function' objects>
|
||||
reveal_type(wrapper_descriptor(f, None, type(f))) # revealed: def f(x: object) -> str
|
||||
static_assert(is_subtype_of(TypeOf[wrapper_descriptor], types.WrapperDescriptorType))
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ class C[T]:
|
|||
return "a"
|
||||
|
||||
reveal_type(getattr_static(C[int], "f")) # revealed: def f(self, x: int) -> str
|
||||
reveal_type(getattr_static(C[int], "f").__get__) # revealed: <method-wrapper `__get__` of `f`>
|
||||
reveal_type(getattr_static(C[int], "f").__get__) # revealed: <method-wrapper '__get__' of function 'f'>
|
||||
reveal_type(getattr_static(C[int], "f").__get__(None, C[int])) # revealed: def f(self, x: int) -> str
|
||||
# revealed: bound method C[int].f(x: int) -> str
|
||||
reveal_type(getattr_static(C[int], "f").__get__(C[int](), C[int]))
|
||||
|
|
|
|||
|
|
@ -271,8 +271,8 @@ method, which means that it is a *data* descriptor (if there is no setter, `__se
|
|||
available but yields an `AttributeError` at runtime).
|
||||
|
||||
```py
|
||||
reveal_type(type(attr_property).__get__) # revealed: <wrapper-descriptor `__get__` of `property` objects>
|
||||
reveal_type(type(attr_property).__set__) # revealed: <wrapper-descriptor `__set__` of `property` objects>
|
||||
reveal_type(type(attr_property).__get__) # revealed: <wrapper-descriptor '__get__' of 'property' objects>
|
||||
reveal_type(type(attr_property).__set__) # revealed: <wrapper-descriptor '__set__' of 'property' objects>
|
||||
```
|
||||
|
||||
When we access `c.attr`, the `__get__` method of the `property` class is called, passing the
|
||||
|
|
|
|||
|
|
@ -8314,6 +8314,7 @@ impl<'db> Type<'db> {
|
|||
KnownInstanceType::TypeAliasType(type_alias) => {
|
||||
type_alias.definition(db).map(TypeDefinition::TypeAlias)
|
||||
}
|
||||
KnownInstanceType::NewType(newtype) => Some(TypeDefinition::NewType(newtype.definition(db))),
|
||||
_ => None,
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use ruff_text_size::{TextLen, TextRange, TextSize};
|
|||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::Db;
|
||||
use crate::place::Place;
|
||||
use crate::types::class::{ClassLiteral, ClassType, GenericAlias};
|
||||
use crate::types::function::{FunctionType, OverloadLiteral};
|
||||
use crate::types::generics::{GenericContext, Specialization};
|
||||
|
|
@ -642,11 +643,13 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
|||
Type::PropertyInstance(_) => f.with_type(self.ty).write_str("property"),
|
||||
Type::ModuleLiteral(module) => {
|
||||
f.set_invalid_syntax();
|
||||
write!(
|
||||
f.with_type(self.ty),
|
||||
"<module '{}'>",
|
||||
module.module(self.db).name(self.db)
|
||||
)
|
||||
f.write_char('<')?;
|
||||
f.with_type(KnownClass::ModuleType.to_class_literal(self.db))
|
||||
.write_str("module")?;
|
||||
f.write_str(" '")?;
|
||||
f.with_type(self.ty)
|
||||
.write_str(module.module(self.db).name(self.db))?;
|
||||
f.write_str("'>")
|
||||
}
|
||||
Type::ClassLiteral(class) => {
|
||||
f.set_invalid_syntax();
|
||||
|
|
@ -692,11 +695,17 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
|||
write!(f.with_type(Type::Dynamic(dynamic)), "{dynamic}")?;
|
||||
f.write_char(']')
|
||||
}
|
||||
SubclassOfInner::TypeVar(bound_typevar) => write!(
|
||||
f,
|
||||
"type[{}]",
|
||||
SubclassOfInner::TypeVar(bound_typevar) => {
|
||||
f.with_type(KnownClass::Type.to_class_literal(self.db))
|
||||
.write_str("type")?;
|
||||
f.write_char('[')?;
|
||||
write!(
|
||||
f.with_type(Type::TypeVar(bound_typevar)),
|
||||
"{}",
|
||||
bound_typevar.identity(self.db).display(self.db)
|
||||
),
|
||||
)?;
|
||||
f.write_char(']')
|
||||
}
|
||||
},
|
||||
Type::SpecialForm(special_form) => {
|
||||
f.set_invalid_syntax();
|
||||
|
|
@ -763,61 +772,115 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
|||
}
|
||||
Type::KnownBoundMethod(method_type) => {
|
||||
f.set_invalid_syntax();
|
||||
match method_type {
|
||||
KnownBoundMethodType::FunctionTypeDunderGet(function) => {
|
||||
write!(
|
||||
f,
|
||||
"<method-wrapper `__get__` of `{function}`>",
|
||||
function = function.name(self.db),
|
||||
)
|
||||
}
|
||||
KnownBoundMethodType::FunctionTypeDunderCall(function) => {
|
||||
write!(
|
||||
f,
|
||||
"<method-wrapper `__call__` of `{function}`>",
|
||||
function = function.name(self.db),
|
||||
)
|
||||
}
|
||||
KnownBoundMethodType::PropertyDunderGet(_) => {
|
||||
f.write_str("<method-wrapper `__get__` of `property` object>")
|
||||
}
|
||||
KnownBoundMethodType::PropertyDunderSet(_) => {
|
||||
f.write_str("<method-wrapper `__set__` of `property` object>")
|
||||
}
|
||||
KnownBoundMethodType::StrStartswith(_) => {
|
||||
f.write_str("<method-wrapper `startswith` of `str` object>")
|
||||
}
|
||||
let (cls, member_name, cls_name, ty, ty_name) = match method_type {
|
||||
KnownBoundMethodType::FunctionTypeDunderGet(function) => (
|
||||
KnownClass::FunctionType,
|
||||
"__get__",
|
||||
"function",
|
||||
Type::FunctionLiteral(function),
|
||||
Some(&**function.name(self.db)),
|
||||
),
|
||||
KnownBoundMethodType::FunctionTypeDunderCall(function) => (
|
||||
KnownClass::FunctionType,
|
||||
"__call__",
|
||||
"function",
|
||||
Type::FunctionLiteral(function),
|
||||
Some(&**function.name(self.db)),
|
||||
),
|
||||
KnownBoundMethodType::PropertyDunderGet(property) => (
|
||||
KnownClass::Property,
|
||||
"__get__",
|
||||
"property",
|
||||
Type::PropertyInstance(property),
|
||||
property
|
||||
.getter(self.db)
|
||||
.and_then(Type::as_function_literal)
|
||||
.map(|getter| &**getter.name(self.db)),
|
||||
),
|
||||
KnownBoundMethodType::PropertyDunderSet(property) => (
|
||||
KnownClass::Property,
|
||||
"__set__",
|
||||
"property",
|
||||
Type::PropertyInstance(property),
|
||||
property
|
||||
.getter(self.db)
|
||||
.and_then(Type::as_function_literal)
|
||||
.map(|getter| &**getter.name(self.db)),
|
||||
),
|
||||
KnownBoundMethodType::StrStartswith(literal) => (
|
||||
KnownClass::Property,
|
||||
"startswith",
|
||||
"string",
|
||||
Type::StringLiteral(literal),
|
||||
Some(literal.value(self.db)),
|
||||
),
|
||||
KnownBoundMethodType::ConstraintSetRange => {
|
||||
f.write_str("bound method `ConstraintSet.range`")
|
||||
return f.write_str("bound method `ConstraintSet.range`");
|
||||
}
|
||||
KnownBoundMethodType::ConstraintSetAlways => {
|
||||
f.write_str("bound method `ConstraintSet.always`")
|
||||
return f.write_str("bound method `ConstraintSet.always`");
|
||||
}
|
||||
KnownBoundMethodType::ConstraintSetNever => {
|
||||
f.write_str("bound method `ConstraintSet.never`")
|
||||
return f.write_str("bound method `ConstraintSet.never`");
|
||||
}
|
||||
KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_) => {
|
||||
f.write_str("bound method `ConstraintSet.implies_subtype_of`")
|
||||
return f.write_str("bound method `ConstraintSet.implies_subtype_of`");
|
||||
}
|
||||
KnownBoundMethodType::ConstraintSetSatisfies(_) => {
|
||||
f.write_str("bound method `ConstraintSet.satisfies`")
|
||||
return f.write_str("bound method `ConstraintSet.satisfies`");
|
||||
}
|
||||
KnownBoundMethodType::ConstraintSetSatisfiedByAllTypeVars(_) => {
|
||||
f.write_str("bound method `ConstraintSet.satisfied_by_all_typevars`")
|
||||
return f
|
||||
.write_str("bound method `ConstraintSet.satisfied_by_all_typevars`");
|
||||
}
|
||||
KnownBoundMethodType::GenericContextSpecializeConstrained(_) => {
|
||||
f.write_str("bound method `GenericContext.specialize_constrained`")
|
||||
return f.write_str("bound method `GenericContext.specialize_constrained`");
|
||||
}
|
||||
};
|
||||
|
||||
let class_ty = cls.to_class_literal(self.db);
|
||||
f.write_char('<')?;
|
||||
f.with_type(KnownClass::MethodWrapperType.to_class_literal(self.db))
|
||||
.write_str("method-wrapper")?;
|
||||
f.write_str(" '")?;
|
||||
if let Place::Defined(member_ty, _, _) = class_ty.member(self.db, member_name).place
|
||||
{
|
||||
f.with_type(member_ty).write_str(member_name)?;
|
||||
} else {
|
||||
f.write_str(member_name)?;
|
||||
}
|
||||
f.write_str("' of ")?;
|
||||
f.with_type(class_ty).write_str(cls_name)?;
|
||||
if let Some(name) = ty_name {
|
||||
f.write_str(" '")?;
|
||||
f.with_type(ty).write_str(name)?;
|
||||
f.write_str("'>")
|
||||
} else {
|
||||
f.write_str("' object>")
|
||||
}
|
||||
}
|
||||
Type::WrapperDescriptor(kind) => {
|
||||
f.set_invalid_syntax();
|
||||
let (method, object) = match kind {
|
||||
WrapperDescriptorKind::FunctionTypeDunderGet => ("__get__", "function"),
|
||||
WrapperDescriptorKind::PropertyDunderGet => ("__get__", "property"),
|
||||
WrapperDescriptorKind::PropertyDunderSet => ("__set__", "property"),
|
||||
let (method, object, cls) = match kind {
|
||||
WrapperDescriptorKind::FunctionTypeDunderGet => {
|
||||
("__get__", "function", KnownClass::FunctionType)
|
||||
}
|
||||
WrapperDescriptorKind::PropertyDunderGet => {
|
||||
("__get__", "property", KnownClass::Property)
|
||||
}
|
||||
WrapperDescriptorKind::PropertyDunderSet => {
|
||||
("__set__", "property", KnownClass::Property)
|
||||
}
|
||||
};
|
||||
write!(f, "<wrapper-descriptor `{method}` of `{object}` objects>")
|
||||
f.write_char('<')?;
|
||||
f.with_type(KnownClass::WrapperDescriptorType.to_class_literal(self.db))
|
||||
.write_str("wrapper-descriptor")?;
|
||||
f.write_str(" '")?;
|
||||
f.write_str(method)?;
|
||||
f.write_str("' of '")?;
|
||||
f.with_type(cls.to_class_literal(self.db))
|
||||
.write_str(object)?;
|
||||
f.write_str("' objects>")
|
||||
}
|
||||
Type::DataclassDecorator(_) => {
|
||||
f.set_invalid_syntax();
|
||||
|
|
@ -907,7 +970,10 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
|||
.fmt_detailed(f),
|
||||
Type::TypedDict(TypedDictType::Synthesized(synthesized)) => {
|
||||
f.set_invalid_syntax();
|
||||
f.write_str("<TypedDict with items ")?;
|
||||
f.write_char('<')?;
|
||||
f.with_type(Type::SpecialForm(SpecialFormType::TypedDict))
|
||||
.write_str("TypedDict")?;
|
||||
f.write_str(" with items ")?;
|
||||
let items = synthesized.items(self.db);
|
||||
for (i, name) in items.keys().enumerate() {
|
||||
let is_last = i == items.len() - 1;
|
||||
|
|
@ -1318,10 +1384,13 @@ impl<'db> DisplayGenericContext<'_, 'db> {
|
|||
f.set_invalid_syntax();
|
||||
let typevar = bound_typevar.typevar(self.db);
|
||||
if typevar.is_paramspec(self.db) {
|
||||
write!(f, "**{}", typevar.name(self.db))?;
|
||||
} else {
|
||||
f.write_str(typevar.name(self.db))?;
|
||||
f.write_str("**")?;
|
||||
}
|
||||
write!(
|
||||
f.with_type(Type::TypeVar(*bound_typevar)),
|
||||
"{}",
|
||||
typevar.name(self.db)
|
||||
)?;
|
||||
}
|
||||
f.write_char(']')
|
||||
}
|
||||
|
|
@ -1334,7 +1403,11 @@ impl<'db> DisplayGenericContext<'_, 'db> {
|
|||
f.write_str(", ")?;
|
||||
}
|
||||
f.set_invalid_syntax();
|
||||
write!(f, "{}", bound_typevar.identity(self.db).display(self.db))?;
|
||||
write!(
|
||||
f.with_type(Type::TypeVar(bound_typevar)),
|
||||
"{}",
|
||||
bound_typevar.identity(self.db).display(self.db)
|
||||
)?;
|
||||
}
|
||||
f.write_char(']')
|
||||
}
|
||||
|
|
@ -2262,15 +2335,17 @@ impl<'db> FmtDetailed<'db> for DisplayKnownInstanceRepr<'db> {
|
|||
KnownInstanceType::SubscriptedProtocol(generic_context) => {
|
||||
f.set_invalid_syntax();
|
||||
f.write_str("<special form '")?;
|
||||
f.with_type(ty).write_str("typing.Protocol")?;
|
||||
f.write_str(&generic_context.display(self.db).to_string())?;
|
||||
f.with_type(Type::SpecialForm(SpecialFormType::Protocol))
|
||||
.write_str("typing.Protocol")?;
|
||||
generic_context.display(self.db).fmt_detailed(f)?;
|
||||
f.write_str("'>")
|
||||
}
|
||||
KnownInstanceType::SubscriptedGeneric(generic_context) => {
|
||||
f.set_invalid_syntax();
|
||||
f.write_str("<special form '")?;
|
||||
f.with_type(ty).write_str("typing.Generic")?;
|
||||
f.write_str(&generic_context.display(self.db).to_string())?;
|
||||
f.with_type(Type::SpecialForm(SpecialFormType::Generic))
|
||||
.write_str("typing.Generic")?;
|
||||
generic_context.display(self.db).fmt_detailed(f)?;
|
||||
f.write_str("'>")
|
||||
}
|
||||
KnownInstanceType::TypeAliasType(alias) => {
|
||||
|
|
@ -2278,15 +2353,9 @@ impl<'db> FmtDetailed<'db> for DisplayKnownInstanceRepr<'db> {
|
|||
f.set_invalid_syntax();
|
||||
f.write_str("<type alias '")?;
|
||||
f.with_type(ty).write_str(alias.name(self.db))?;
|
||||
f.write_str(
|
||||
&specialization
|
||||
.display_short(
|
||||
self.db,
|
||||
TupleSpecialization::No,
|
||||
DisplaySettings::default(),
|
||||
)
|
||||
.to_string(),
|
||||
)?;
|
||||
specialization
|
||||
.display_short(self.db, TupleSpecialization::No, DisplaySettings::default())
|
||||
.fmt_detailed(f)?;
|
||||
f.write_str("'>")
|
||||
} else {
|
||||
f.with_type(ty).write_str("typing.TypeAliasType")
|
||||
|
|
@ -2306,7 +2375,9 @@ impl<'db> FmtDetailed<'db> for DisplayKnownInstanceRepr<'db> {
|
|||
KnownInstanceType::Field(field) => {
|
||||
f.with_type(ty).write_str("dataclasses.Field")?;
|
||||
if let Some(default_ty) = field.default_type(self.db) {
|
||||
write!(f, "[{}]", default_ty.display(self.db))?;
|
||||
f.write_char('[')?;
|
||||
write!(f.with_type(default_ty), "{}", default_ty.display(self.db))?;
|
||||
f.write_char(']')?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -2325,51 +2396,58 @@ impl<'db> FmtDetailed<'db> for DisplayKnownInstanceRepr<'db> {
|
|||
KnownInstanceType::UnionType(union) => {
|
||||
f.set_invalid_syntax();
|
||||
f.write_char('<')?;
|
||||
f.with_type(ty).write_str("types.UnionType")?;
|
||||
f.with_type(KnownClass::UnionType.to_class_literal(self.db))
|
||||
.write_str("types.UnionType")?;
|
||||
f.write_str(" special form")?;
|
||||
if let Ok(ty) = union.union_type(self.db) {
|
||||
write!(f, " '{}'", ty.display(self.db))?;
|
||||
f.write_str(" '")?;
|
||||
ty.display(self.db).fmt_detailed(f)?;
|
||||
f.write_char('\'')?;
|
||||
}
|
||||
f.write_char('>')
|
||||
}
|
||||
KnownInstanceType::Literal(inner) => {
|
||||
f.set_invalid_syntax();
|
||||
write!(
|
||||
f,
|
||||
"<special form '{}'>",
|
||||
inner.inner(self.db).display(self.db)
|
||||
)
|
||||
f.write_str("<special form '")?;
|
||||
inner.inner(self.db).display(self.db).fmt_detailed(f)?;
|
||||
f.write_str("'>")
|
||||
}
|
||||
KnownInstanceType::Annotated(inner) => {
|
||||
f.set_invalid_syntax();
|
||||
f.write_str("<special form '")?;
|
||||
f.with_type(ty).write_str("typing.Annotated")?;
|
||||
write!(
|
||||
f,
|
||||
"[{}, <metadata>]'>",
|
||||
inner.inner(self.db).display(self.db)
|
||||
)
|
||||
f.with_type(Type::SpecialForm(SpecialFormType::Annotated))
|
||||
.write_str("typing.Annotated")?;
|
||||
f.write_char('[')?;
|
||||
inner.inner(self.db).display(self.db).fmt_detailed(f)?;
|
||||
f.write_str(", <metadata>]'>")
|
||||
}
|
||||
KnownInstanceType::Callable(callable) => {
|
||||
f.set_invalid_syntax();
|
||||
f.write_char('<')?;
|
||||
f.with_type(ty).write_str("typing.Callable")?;
|
||||
write!(f, " special form '{}'>", callable.display(self.db))
|
||||
f.with_type(Type::SpecialForm(SpecialFormType::Callable))
|
||||
.write_str("typing.Callable")?;
|
||||
f.write_str(" special form '")?;
|
||||
callable.display(self.db).fmt_detailed(f)?;
|
||||
f.write_str("'>")
|
||||
}
|
||||
KnownInstanceType::TypeGenericAlias(inner) => {
|
||||
f.set_invalid_syntax();
|
||||
f.write_str("<special form '")?;
|
||||
write!(
|
||||
f.with_type(ty),
|
||||
"type[{}]",
|
||||
inner.inner(self.db).display(self.db)
|
||||
)?;
|
||||
f.write_str("'>")
|
||||
f.with_type(KnownClass::Type.to_class_literal(self.db))
|
||||
.write_str("type")?;
|
||||
f.write_char('[')?;
|
||||
inner.inner(self.db).display(self.db).fmt_detailed(f)?;
|
||||
f.write_str("]'>")
|
||||
}
|
||||
KnownInstanceType::LiteralStringAlias(_) => f.write_str("str"),
|
||||
KnownInstanceType::LiteralStringAlias(_) => f
|
||||
.with_type(KnownClass::Str.to_class_literal(self.db))
|
||||
.write_str("str"),
|
||||
KnownInstanceType::NewType(declaration) => {
|
||||
f.set_invalid_syntax();
|
||||
f.write_str("<NewType pseudo-class '")?;
|
||||
f.write_char('<')?;
|
||||
f.with_type(KnownClass::NewType.to_class_literal(self.db))
|
||||
.write_str("NewType")?;
|
||||
f.write_str(" pseudo-class '")?;
|
||||
f.with_type(ty).write_str(declaration.name(self.db))?;
|
||||
f.write_str("'>")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue