Skip to content
Draft
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
131 changes: 131 additions & 0 deletions crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,137 @@ def f(x: MyInt):
f(1)
```

## Forward-referenced class aliases in stubs

A class alias in a stub may refer to a class defined later in the same file, and another alias can
reuse the same forward reference.

`aliases.pyi`:

```pyi
Alias = Target
ChainedAlias = Alias

class Target: ...

def accepts(value: Alias) -> Alias: ...
```

The imported aliases resolve correctly as values and in type expressions.

`main.py`:

```py
from aliases import Alias, ChainedAlias, Target, accepts

reveal_type(Alias) # revealed: <class 'Target'>
reveal_type(ChainedAlias) # revealed: <class 'Target'>
reveal_type(accepts(Target())) # revealed: Target

def use_alias(value: Alias) -> None:
reveal_type(value) # revealed: Target
```

## Forward-referenced union aliases in stubs

A union alias in a stub may refer to classes defined after the assignment.

```pyi
UnionAlias = Target | Other

class Target: ...
class Other: ...

reveal_type(UnionAlias) # revealed: <types.UnionType special-form 'Target | Other'>

def use_alias(value: UnionAlias) -> None:
reveal_type(value) # revealed: Target | Other
```

## Forward-referenced values in stubs

A stub assignment may refer to an ordinary value defined later in the same file.

```pyi
AliasValue = VALUE

VALUE = 7

reveal_type(AliasValue) # revealed: Literal[7]
```

## Stub assignments preserve existing bindings

An already-defined name is resolved at the assignment even if it is rebound later.

```pyi
VALUE = 1
Alias = VALUE
VALUE = 2

reveal_type(Alias) # revealed: Literal[1]
reveal_type(VALUE) # revealed: Literal[2]
```

## Self-referential stub reassignments

Reassigning a stub variable to itself should resolve its previous binding without creating a cycle.

```pyi
SelfValue = 3
SelfValue = SelfValue

reveal_type(SelfValue) # revealed: Literal[3]
```

## Forward-referenced aliases as generic bounds

A forward-referenced stub alias used as a generic bound must reject incompatible arguments during
overload selection.

```toml
[environment]
python-version = "3.12"
```

`aliases.pyi`:

```pyi
from typing import overload

Alias = Target

class Target: ...

@overload
def choose[T: Alias](value: T) -> T: ...
@overload
def choose(value: int) -> str: ...
```

The first overload accepts instances of its bound, while an incompatible integer selects the second
overload.

`main.py`:

```py
from aliases import Target, choose

reveal_type(choose(Target())) # revealed: Target
reveal_type(choose(1)) # revealed: str
```

## Forward references in runtime modules

Assignments in runtime Python modules are evaluated eagerly, so a class defined later is not yet
available.

```py
Alias = Target # error: [unresolved-reference]

class Target: ...
```

## None

```py
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,9 @@ reveal_type(custom_builtin) # revealed: Custom
reveal_type(str) # revealed: Unknown
```

## Unknown builtin (later defined)
## Forward reference in a builtins stub

`foo` has a type of `Unknown` in this example, as it relies on `bar` which has not been defined at
that point:
A simple assignment in a builtins stub may refer to a value defined later in the same file.

```toml
[environment]
Expand All @@ -278,7 +277,7 @@ def reveal_type(obj, /): ...
```

```py
reveal_type(foo) # revealed: Unknown
reveal_type(foo) # revealed: Literal[1]
```

## Builtins imported from custom project-level stubs
Expand Down
38 changes: 38 additions & 0 deletions crates/ty_python_semantic/resources/mdtest/overloads.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,44 @@ reveal_type(foo3.takes_self_or_int(foo3)) # revealed: Foo3
reveal_type(foo3.takes_self_or_int(1)) # revealed: int
```

## Aliases of conditionally overloaded methods in stubs

An alias assigned to an overloaded stub method retains every applicable overload across separate
platform-specific branches.

```toml
[environment]
python-platform = "linux"
```

```pyi
import sys
from typing import overload

class Window:
if sys.platform == "darwin":
@overload
def method(self) -> str: ...

else:
@overload
def method(self) -> int: ...

if sys.platform == "darwin":
@overload
def method(self, value: bytes) -> bytes: ...

else:
@overload
def method(self, value: float) -> float: ...

alias = method

window: Window
reveal_type(window.alias) # revealed: Overload[() -> int, (value: float) -> float]
reveal_type(window.alias(1.0)) # revealed: float
```

## Explicit receiver annotations

Binding a method filters overloads that explicitly annotate `self` with a type that cannot accept
Expand Down
12 changes: 12 additions & 0 deletions crates/ty_python_semantic/src/types/infer/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3386,6 +3386,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {

self.store_expression_type(value, ty);
ty
} else if self.in_stub()
&& value.as_name_expr().is_none_or(|name| {
// Already-bound overloaded functions need their at-use binding set.
!name.is_invalid()
&& self
.index
.use_def_map(self.scope().file_scope_id(self.db()))
.bindings_at_use(name.scoped_use_id(self.db(), self.program_file()))
.all(|binding| binding.binding.definition().is_none())
})
{
self.infer_expression_with_state(value, tcx, DeferredExpressionState::Deferred)
} else {
self.infer_expression(value, tcx)
};
Expand Down
Loading