Compare commits

...

3 Commits

Author SHA1 Message Date
Douglas Creager
3782ccf028 walk the scopes 2025-05-19 18:10:10 -04:00
Douglas Creager
5945ca6fce TODO: here, add legacy typevar binding generic context 2025-05-19 18:09:52 -04:00
Douglas Creager
b5456c9395 generic_context extension for functions 2025-05-19 17:00:59 -04:00
6 changed files with 116 additions and 14 deletions

View File

@@ -420,17 +420,30 @@ the typevars of the enclosing generic class, and introduce new (distinct) typeva
scope for the method.
```py
from ty_extensions import generic_context
from typing import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U")
class C(Generic[T]):
def method(self, u: U) -> U:
def method(self, u: int) -> int:
return u
def generic_method(self, t: T, u: U) -> U:
return u
reveal_type(generic_context(C)) # revealed: tuple[T]
reveal_type(generic_context(C.method)) # revealed: None
# TODO: revealed: tuple[U]
reveal_type(generic_context(C.generic_method)) # revealed: tuple[T, U]
reveal_type(generic_context(C[int])) # revealed: None
reveal_type(generic_context(C[int].method)) # revealed: None
# TODO: revealed: tuple[U]
reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[T, U]
c: C[int] = C[int]()
reveal_type(c.method("string")) # revealed: Literal["string"]
reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"]
```
## Specializations propagate

View File

@@ -345,8 +345,13 @@ the typevars of the enclosing generic class, and introduce new (distinct) typeva
scope for the method.
```py
from ty_extensions import generic_context
class C[T]:
def method[U](self, u: U) -> U:
def method(self, u: int) -> int:
return u
def generic_method[U](self, t: T, u: U) -> U:
return u
# error: [unresolved-reference]
def cannot_use_outside_of_method(self, u: U): ...
@@ -354,8 +359,15 @@ class C[T]:
# TODO: error
def cannot_shadow_class_typevar[T](self, t: T): ...
reveal_type(generic_context(C)) # revealed: tuple[T]
reveal_type(generic_context(C.method)) # revealed: None
reveal_type(generic_context(C.generic_method)) # revealed: tuple[U]
reveal_type(generic_context(C[int])) # revealed: None
reveal_type(generic_context(C[int].method)) # revealed: None
reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[U]
c: C[int] = C[int]()
reveal_type(c.method("string")) # revealed: Literal["string"]
reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"]
```
## Specializations propagate

View File

@@ -6867,6 +6867,10 @@ impl<'db> FunctionType<'db> {
}
}
pub(crate) fn non_overloaded_signature(self, db: &'db dyn Db) -> Signature<'db> {
self.internal_signature(db, self.inherited_generic_context(db))
}
/// Typed internally-visible signature for this function.
///
/// This represents the annotations on the function itself, unmodified by decorators and

View File

@@ -591,16 +591,28 @@ impl<'db> Bindings<'db> {
// TODO: Handle generic functions, and unions/intersections of
// generic types
overload.set_return_type(match ty {
Type::ClassLiteral(class) => match class.generic_context(db) {
Some(generic_context) => TupleType::from_elements(
Type::ClassLiteral(class) => (class.generic_context(db))
.map(|generic_context| generic_context.as_tuple(db))
.unwrap_or(Type::none(db)),
Type::FunctionLiteral(function) => {
let union = UnionType::from_elements(
db,
generic_context
.variables(db)
function
.signature(db)
.overloads
.iter()
.map(|typevar| Type::TypeVar(*typevar)),
),
None => Type::none(db),
},
.filter_map(|signature| signature.generic_context)
.map(|generic_context| {
generic_context.as_tuple(db)
}),
);
if union.is_never() {
Type::none(db)
} else {
union
}
}
_ => Type::none(db),
});

View File

@@ -9,7 +9,7 @@ use crate::types::class_base::ClassBase;
use crate::types::instance::{NominalInstanceType, Protocol, ProtocolInstanceType};
use crate::types::signatures::{Parameter, Parameters, Signature};
use crate::types::{
KnownInstanceType, Type, TypeMapping, TypeVarBoundOrConstraints, TypeVarInstance,
KnownInstanceType, TupleType, Type, TypeMapping, TypeVarBoundOrConstraints, TypeVarInstance,
TypeVarVariance, UnionType, declaration_type, todo_type,
};
use crate::{Db, FxOrderSet};
@@ -105,6 +105,15 @@ impl<'db> GenericContext<'db> {
Some(Self::new(db, variables, GenericContextOrigin::Inherited))
}
pub(crate) fn is_legacy(self, db: &'db dyn Db) -> bool {
matches!(
self.origin(db),
GenericContextOrigin::LegacyBase(_)
| GenericContextOrigin::Inherited
| GenericContextOrigin::LegacyGenericFunction
)
}
pub(crate) fn len(self, db: &'db dyn Db) -> usize {
self.variables(db).len()
}
@@ -171,6 +180,16 @@ impl<'db> GenericContext<'db> {
self.specialize(db, types.into())
}
/// Returns a tuple type of the typevars introduced by this generic context.
pub(crate) fn as_tuple(self, db: &'db dyn Db) -> Type<'db> {
TupleType::from_elements(
db,
self.variables(db)
.iter()
.map(|typevar| Type::TypeVar(*typevar)),
)
}
pub(crate) fn is_subset_of(self, db: &'db dyn Db, other: GenericContext<'db>) -> bool {
self.variables(db).is_subset(other.variables(db))
}

View File

@@ -355,6 +355,42 @@ pub(crate) fn enclosing_class_symbol<'db>(
})
}
/// Returns in iterator of any generic context introduced by the given scope or any enclosing
/// scope.
pub(crate) fn enclosing_generic_contexts<'db>(
db: &'db dyn Db,
index: &SemanticIndex<'db>,
scope: ScopeId,
) -> impl Iterator<Item = GenericContext<'db>> {
index
.ancestor_scopes(scope.file_scope_id(db))
.filter_map(|(_, ancestor_scope)| match ancestor_scope.node() {
NodeWithScopeKind::Class(class) => {
binding_type(db, index.expect_single_definition(class.node()))
.into_class_literal()?
.generic_context(db)
}
NodeWithScopeKind::Function(function) => {
binding_type(db, index.expect_single_definition(function.node()))
.into_function_literal()?
.non_overloaded_signature(db)
.generic_context
}
_ => None,
})
}
/// Returns the legacy typevars that have been bound in the given scope or any enclosing scope.
pub(crate) fn bound_legacy_typevars<'db>(
db: &'db dyn Db,
index: &'db SemanticIndex<'db>,
scope: ScopeId,
) -> impl Iterator<Item = TypeVarInstance<'db>> {
enclosing_generic_contexts(db, index, scope)
.filter(|generic_context| generic_context.is_legacy(db))
.flat_map(|generic_context| generic_context.variables(db).iter().copied())
}
/// A region within which we can infer types.
#[derive(Copy, Clone, Debug)]
pub(crate) enum InferenceRegion<'db> {
@@ -4233,7 +4269,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}
fn infer_expression_impl(&mut self, expression: &ast::Expr) -> Type<'db> {
let ty = match expression {
let mut ty = match expression {
ast::Expr::NoneLiteral(ast::ExprNoneLiteral { range: _ }) => Type::none(self.db()),
ast::Expr::NumberLiteral(literal) => self.infer_number_literal_expression(literal),
ast::Expr::BooleanLiteral(literal) => self.infer_boolean_literal_expression(literal),
@@ -4274,6 +4310,12 @@ impl<'db> TypeInferenceBuilder<'db> {
}
};
// If the expression resolves to a legacy typevar, we will have the TypeVarInstance that
// was created when the typevar was created, which will not have an associated definition.
// Walk the enclosing scopes, looking for the nearest generic context that binds the
// typevar.
if let Type::TypeVar(typevar) = f
self.store_expression_type(expression, ty);
ty