diff --git a/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md b/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md index d1cb3caba1ea7d..74e515c37d48d3 100644 --- a/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md @@ -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: +reveal_type(ChainedAlias) # revealed: +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: + +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 diff --git a/crates/ty_python_semantic/resources/mdtest/import/builtins.md b/crates/ty_python_semantic/resources/mdtest/import/builtins.md index 566911797660bf..97092f751f43e8 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/builtins.md +++ b/crates/ty_python_semantic/resources/mdtest/import/builtins.md @@ -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] @@ -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 diff --git a/crates/ty_python_semantic/resources/mdtest/overloads.md b/crates/ty_python_semantic/resources/mdtest/overloads.md index 8b3f9cdc04099c..454a35eff1a578 100644 --- a/crates/ty_python_semantic/resources/mdtest/overloads.md +++ b/crates/ty_python_semantic/resources/mdtest/overloads.md @@ -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 diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index b8c5448e318ce2..1ce5fdb3e8e888 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -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) };