Skip to content

Commit 7778f65

Browse files
authored
[ty] Fix upcasting type[T] types to Callable types (#23472)
1 parent fc1081a commit 7778f65

3 files changed

Lines changed: 157 additions & 23 deletions

File tree

crates/ty_python_semantic/resources/mdtest/type_of/generics.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,3 +491,98 @@ expects_type_c_default(C[int])
491491
expects_type_c_default_of_int(C[str])
492492
expects_type_c_default_of_int_str(C[str, int])
493493
```
494+
495+
## Upcasting a `type[]` type to a `Callable` type
496+
497+
`type[T]` accepts the same parameters as `object.__init__` if `T` does not have an upper bound. If
498+
`T` is bound to a nominal-instance type, `type[T]` accepts the same parameters as the constructor of
499+
the class that the instance-type refers to.
500+
501+
```py
502+
from ty_extensions import CallableTypeOf
503+
504+
class TakesStrInConstructor:
505+
def __init__(self, x: int, y: str | None = None): ...
506+
507+
class TakesIntInConstructor:
508+
def __init__(self, x: int, y: int | None = None): ...
509+
510+
def f[
511+
T,
512+
T1: object,
513+
T2: int,
514+
T3: TakesStrInConstructor | TakesIntInConstructor,
515+
T4: (TakesStrInConstructor, TakesIntInConstructor),
516+
](
517+
bare_type: type,
518+
type_object: type[object],
519+
type_t_unbound: type[T],
520+
type_t_object_bound: type[T1],
521+
type_int: type[int],
522+
type_t_int_bound: type[T2],
523+
type_t_union_bound: type[T3],
524+
type_t_constrained: type[T4],
525+
):
526+
# TODO: these are all `Any` because of typeshed's signature for `type.__call__`.
527+
# We could consider overriding that.
528+
reveal_type(bare_type()) # revealed: Any
529+
reveal_type(type_object()) # revealed: Any
530+
531+
# TODO: we could consider emitting errors for these two, but don't currently,
532+
# for the same reason
533+
reveal_type(bare_type("")) # revealed: Any
534+
reveal_type(type_object("")) # revealed: Any
535+
536+
reveal_type(type_t_unbound()) # revealed: T@f
537+
reveal_type(type_t_unbound("")) # revealed: T@f
538+
539+
reveal_type(type_int()) # revealed: int
540+
reveal_type(type_int("1")) # revealed: int
541+
# error: [invalid-argument-type]
542+
reveal_type(type_int([])) # revealed: int
543+
544+
reveal_type(type_t_union_bound(42)) # revealed: T3@f
545+
# error: [invalid-argument-type]
546+
reveal_type(type_t_union_bound(42, "")) # revealed: T3@f
547+
# error: [invalid-argument-type]
548+
reveal_type(type_t_union_bound(42, 42)) # revealed: T3@f
549+
550+
reveal_type(type_t_constrained(42)) # revealed: T4@f
551+
# error: [invalid-argument-type]
552+
reveal_type(type_t_constrained(42, "")) # revealed: T4@f
553+
# error: [invalid-argument-type]
554+
reveal_type(type_t_constrained(42, 42)) # revealed: T4@f
555+
556+
def g(
557+
object_class_upcast: CallableTypeOf[object],
558+
bare_type_upcast: CallableTypeOf[bare_type],
559+
type_object_upcast: CallableTypeOf[type_object],
560+
type_t_unbound_upcast: CallableTypeOf[type_t_unbound],
561+
type_t_object_bound_upcast: CallableTypeOf[type_t_object_bound],
562+
type_int_upcast: CallableTypeOf[type_int],
563+
type_t_int_bound_upcast: CallableTypeOf[type_t_int_bound],
564+
type_t_union_bound_upcast: CallableTypeOf[type_t_union_bound],
565+
type_t_constrained_upcast: CallableTypeOf[type_t_constrained],
566+
):
567+
reveal_type(object_class_upcast) # revealed: () -> object
568+
569+
# TODO: these two could arguably be `() -> object`,
570+
# but have more dynamic signatures due to typeshed's `type.__call__` annotations.
571+
# We could consider overriding those.
572+
reveal_type(bare_type_upcast) # revealed: (...) -> Any
573+
reveal_type(type_object_upcast) # revealed: (...) -> Any
574+
575+
# TODO: if we did decide to override typeshed's `type.__call__` annotations (see above),
576+
# we should also turn these two into `() -> T@f` / `() -> T1@f`
577+
reveal_type(type_t_unbound_upcast) # revealed: (...) -> T@f
578+
reveal_type(type_t_object_bound_upcast) # revealed: (...) -> T1@f
579+
580+
# revealed: Overload[(x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = 0, /) -> int, (x: str | bytes | bytearray, /, base: SupportsIndex) -> int]
581+
reveal_type(type_int_upcast)
582+
# revealed: Overload[(x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = 0, /) -> T2@f, (x: str | bytes | bytearray, /, base: SupportsIndex) -> T2@f]
583+
reveal_type(type_t_int_bound_upcast)
584+
# revealed: ((x: int, y: str | None = None) -> T3@f) | ((x: int, y: int | None = None) -> T3@f)
585+
reveal_type(type_t_union_bound_upcast)
586+
# revealed: ((x: int, y: str | None = None) -> T4@f) | ((x: int, y: int | None = None) -> T4@f)
587+
reveal_type(type_t_constrained_upcast)
588+
```

crates/ty_python_semantic/src/types.rs

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2092,13 +2092,49 @@ impl<'db> Type<'db> {
20922092
// TODO: This is unsound so in future we can consider an opt-in option to disable it.
20932093
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
20942094
SubclassOfInner::Class(class) => Some(class.into_callable(db)),
2095-
2096-
SubclassOfInner::Dynamic(_) | SubclassOfInner::TypeVar(_) => {
2097-
Some(CallableTypes::one(CallableType::single(
2095+
SubclassOfInner::TypeVar(tvar) => match tvar.typevar(db).bound_or_constraints(db) {
2096+
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
2097+
let upcast_callables = bound.to_meta_type(db).try_upcast_to_callable(db)?;
2098+
Some(upcast_callables.map(|callable| {
2099+
let signatures = callable
2100+
.signatures(db)
2101+
.into_iter()
2102+
.map(|sig| sig.clone().with_return_type(Type::TypeVar(tvar)));
2103+
CallableType::new(
2104+
db,
2105+
CallableSignature::from_overloads(signatures),
2106+
callable.kind(db),
2107+
)
2108+
}))
2109+
}
2110+
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
2111+
let mut callables = SmallVec::new();
2112+
for constraint in constraints.elements(db) {
2113+
let element_upcast =
2114+
constraint.to_meta_type(db).try_upcast_to_callable(db)?;
2115+
for callable in element_upcast.into_inner() {
2116+
let signatures = callable
2117+
.signatures(db)
2118+
.into_iter()
2119+
.map(|sig| sig.clone().with_return_type(Type::TypeVar(tvar)));
2120+
callables.push(CallableType::new(
2121+
db,
2122+
CallableSignature::from_overloads(signatures),
2123+
callable.kind(db),
2124+
));
2125+
}
2126+
}
2127+
Some(CallableTypes(callables))
2128+
}
2129+
None => Some(CallableTypes::one(CallableType::single(
20982130
db,
2099-
Signature::new(Parameters::unknown(), Type::from(subclass_of_ty)),
2100-
)))
2101-
}
2131+
Signature::new(Parameters::gradual_form(), Type::TypeVar(tvar)),
2132+
))),
2133+
},
2134+
SubclassOfInner::Dynamic(_) => Some(CallableTypes::one(CallableType::single(
2135+
db,
2136+
Signature::new(Parameters::unknown(), Type::from(subclass_of_ty)),
2137+
))),
21022138
},
21032139

21042140
Type::Union(union) => {
@@ -4287,25 +4323,23 @@ impl<'db> Type<'db> {
42874323
Binding::single(self, Signature::dynamic(Type::Dynamic(dynamic_type))).into()
42884324
}
42894325
SubclassOfInner::Class(class) => self.constructor_bindings(db, class),
4290-
SubclassOfInner::TypeVar(bound_typevar) => {
4291-
let Some(class) = (match bound_typevar.typevar(db).bound_or_constraints(db) {
4292-
None | Some(TypeVarBoundOrConstraints::UpperBound(_)) => {
4293-
subclass_of_type.subclass_of().into_class(db)
4326+
SubclassOfInner::TypeVar(tvar) => {
4327+
let bindings = match tvar.typevar(db).bound_or_constraints(db) {
4328+
None => KnownClass::Type.to_instance(db).bindings(db),
4329+
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
4330+
bound.to_meta_type(db).bindings(db)
4331+
}
4332+
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
4333+
Bindings::from_union(
4334+
self,
4335+
constraints
4336+
.elements(db)
4337+
.iter()
4338+
.map(|ty| ty.to_meta_type(db).bindings(db)),
4339+
)
42944340
}
4295-
// TODO: model calls to `type[T]` where `T` is constrained
4296-
Some(TypeVarBoundOrConstraints::Constraints(_)) => None,
4297-
}) else {
4298-
return Binding::single(
4299-
self,
4300-
Signature::new(
4301-
Parameters::gradual_form(),
4302-
self.to_instance(db).unwrap_or(Type::unknown()),
4303-
),
4304-
)
4305-
.into();
43064341
};
4307-
4308-
self.constructor_bindings(db, class)
4342+
bindings.with_constructor_instance_type(Type::TypeVar(tvar))
43094343
}
43104344
},
43114345

crates/ty_python_semantic/src/types/signatures.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,6 +1715,11 @@ impl<'db> Signature<'db> {
17151715
pub(crate) fn with_definition(self, definition: Option<Definition<'db>>) -> Self {
17161716
Self { definition, ..self }
17171717
}
1718+
1719+
/// Create a new signature with the given return type.
1720+
pub(crate) fn with_return_type(self, return_ty: Type<'db>) -> Self {
1721+
Self { return_ty, ..self }
1722+
}
17181723
}
17191724

17201725
impl<'db> VarianceInferable<'db> for &Signature<'db> {

0 commit comments

Comments
 (0)