Skip to content

Commit 25a3191

Browse files
[ty] Refine Callable class-decorator fallback for unknown results (#25250)
## Summary This PR addresses a TODO from #25091 whereby we preserved the class binding for every `Callable` decorator. We now respect explicit return annotations on `Callable`. For example, this now remains a replacement rather than being treated as class-preserving: ```python from typing import Callable, TypeVar T = TypeVar("T") def decorator_factory() -> Callable[[type[object]], T]: raise NotImplementedError @decorator_factory() class Decorated: ... reveal_type(Decorated) # Unknown ```
1 parent c423054 commit 25a3191

13 files changed

Lines changed: 262 additions & 54 deletions

File tree

crates/ty_python_semantic/resources/mdtest/decorators.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,26 @@ specialized_explicit_return_decorator = ExplicitReturnDecorator[int]()
535535
class SpecializedExplicitReturnCallableInstanceDecorated: ...
536536

537537
reveal_type(SpecializedExplicitReturnCallableInstanceDecorated) # revealed: int
538+
539+
def function_decorator(func: Callable[..., T]) -> Callable[..., T]:
540+
return func
541+
542+
@function_decorator
543+
def explicit_return_callable_decorator(cls) -> T:
544+
raise NotImplementedError
545+
546+
@explicit_return_callable_decorator
547+
class ExplicitReturnCallableDecorated: ...
548+
549+
reveal_type(ExplicitReturnCallableDecorated) # revealed: Unknown
550+
551+
def regular_callable_replacement_factory() -> Callable[[type[object]], T]:
552+
raise NotImplementedError
553+
554+
@regular_callable_replacement_factory()
555+
class RegularCallableReplacementDecorated: ...
556+
557+
reveal_type(RegularCallableReplacementDecorated) # revealed: Unknown
538558
```
539559

540560
An unknown class decorator still makes the class binding unknown:

crates/ty_python_semantic/src/types/call/bind.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use crate::dunder_all::dunder_all_names;
2828
use crate::place::{DefinedPlace, Definedness, Place, known_module_symbol};
2929
use crate::subscript::PyIndex;
3030
use crate::types::call::arguments::{CallArgumentTypes, Expansion, is_expandable_type};
31-
use crate::types::callable::CallableTypeKind;
31+
use crate::types::callable::{CallableFunctionProvenance, CallableTypeKind};
3232
use crate::types::constraints::{ConstraintSet, ConstraintSetBuilder, PathBounds, Solutions};
3333
use crate::types::diagnostic::{
3434
CALL_NON_CALLABLE, CALL_TOP_CALLABLE, CONFLICTING_ARGUMENT_FORMS, INVALID_ARGUMENT_TYPE,
@@ -7483,6 +7483,7 @@ fn asynccontextmanager_return_type<'db>(db: &'db dyn Db, func_ty: Type<'db>) ->
74837483
db,
74847484
CallableSignature::single(new_signature),
74857485
CallableTypeKind::FunctionLike,
7486+
CallableFunctionProvenance::None,
74867487
)))
74877488
}
74887489

crates/ty_python_semantic/src/types/callable.rs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ impl<'db> Type<'db> {
163163
db,
164164
CallableSignature::from_overloads(signatures),
165165
callable.kind(db),
166+
callable.provenance(db),
166167
)
167168
}))
168169
}
@@ -183,6 +184,7 @@ impl<'db> Type<'db> {
183184
db,
184185
CallableSignature::from_overloads(signatures),
185186
callable.kind(db),
187+
callable.provenance(db),
186188
));
187189
}
188190
}
@@ -230,13 +232,15 @@ impl<'db> Type<'db> {
230232
db,
231233
CallableSignature::from_overloads(method.signatures(db)),
232234
CallableTypeKind::Regular,
235+
CallableFunctionProvenance::None,
233236
))),
234237

235238
Type::WrapperDescriptor(wrapper_descriptor) => {
236239
Some(CallableTypes::one(CallableType::new(
237240
db,
238241
CallableSignature::from_overloads(wrapper_descriptor.signatures(db)),
239242
CallableTypeKind::Regular,
243+
CallableFunctionProvenance::None,
240244
)))
241245
}
242246

@@ -324,6 +328,33 @@ pub enum CallableTypeKind {
324328
ParamSpecValue,
325329
}
326330

331+
/// Source-function provenance retained by a callable signature.
332+
///
333+
/// A [`CallableType`] can describe a bare callable shape, such as one from `Callable[...]`. For
334+
/// function-like sources, such as a [`FunctionType`] upcast to a [`CallableType`] or a lambda, this
335+
/// records whether the source function has an explicit return annotation.
336+
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
337+
pub enum CallableFunctionProvenance {
338+
/// The callable does not retain source-function provenance.
339+
None,
340+
341+
/// The callable came from a function without an explicit return annotation.
342+
ImplicitReturn,
343+
344+
/// The callable came from a function with an explicit return annotation.
345+
ExplicitReturn,
346+
}
347+
348+
impl CallableFunctionProvenance {
349+
pub(crate) fn from_function_return_annotation(has_explicit_return_annotation: bool) -> Self {
350+
if has_explicit_return_annotation {
351+
Self::ExplicitReturn
352+
} else {
353+
Self::ImplicitReturn
354+
}
355+
}
356+
}
357+
327358
/// A "policy" enum that describes how `type[]` types should be upcast
328359
/// to `Callable` types.
329360
///
@@ -380,6 +411,20 @@ pub struct CallableType<'db> {
380411
pub(crate) signatures: CallableSignature<'db>,
381412

382413
pub(super) kind: CallableTypeKind,
414+
415+
/// Source-function return-annotation provenance retained by this callable.
416+
///
417+
/// Function-like values can retain their source-function provenance when converted to a
418+
/// callable signature:
419+
/// ```python
420+
/// def decorator(cls) -> object: ...
421+
/// ```
422+
///
423+
/// Callables that are only known from a callable shape do not retain that provenance:
424+
/// ```python
425+
/// def decorator_factory() -> Callable[[type[object]], object]: ...
426+
/// ```
427+
pub(crate) provenance: CallableFunctionProvenance,
383428
}
384429

385430
pub(super) fn walk_callable_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
@@ -401,6 +446,7 @@ impl<'db> CallableType<'db> {
401446
db,
402447
CallableSignature::single(signature),
403448
CallableTypeKind::Regular,
449+
CallableFunctionProvenance::None,
404450
)
405451
}
406452

@@ -409,6 +455,7 @@ impl<'db> CallableType<'db> {
409455
db,
410456
CallableSignature::single(signature),
411457
CallableTypeKind::FunctionLike,
458+
CallableFunctionProvenance::None,
412459
)
413460
}
414461

@@ -420,6 +467,7 @@ impl<'db> CallableType<'db> {
420467
db,
421468
CallableSignature::single(Signature::new(parameters, Type::unknown())),
422469
CallableTypeKind::ParamSpecValue,
470+
CallableFunctionProvenance::None,
423471
)
424472
}
425473

@@ -441,7 +489,12 @@ impl<'db> CallableType<'db> {
441489
}
442490

443491
pub(crate) fn into_regular(self, db: &'db dyn Db) -> CallableType<'db> {
444-
CallableType::new(db, self.signatures(db), CallableTypeKind::Regular)
492+
CallableType::new(
493+
db,
494+
self.signatures(db),
495+
CallableTypeKind::Regular,
496+
self.provenance(db),
497+
)
445498
}
446499

447500
/// Returns the reduced callable produced by partially applying selected overloads.
@@ -453,6 +506,7 @@ impl<'db> CallableType<'db> {
453506
db,
454507
CallableSignature::partially_apply(db, overloads)?,
455508
CallableTypeKind::Regular,
509+
CallableFunctionProvenance::None,
456510
))
457511
}
458512

@@ -482,6 +536,7 @@ impl<'db> CallableType<'db> {
482536
db,
483537
self.signatures(db).bind_self(db, self_type),
484538
self.kind(db),
539+
self.provenance(db),
485540
)
486541
}
487542

@@ -490,6 +545,7 @@ impl<'db> CallableType<'db> {
490545
db,
491546
self.signatures(db).apply_self(db, self_type),
492547
self.kind(db),
548+
self.provenance(db),
493549
)
494550
}
495551

@@ -498,7 +554,12 @@ impl<'db> CallableType<'db> {
498554
/// Specifically, this represents a callable type with a single signature:
499555
/// `(*args: object, **kwargs: object) -> Never`.
500556
pub(crate) fn bottom(db: &'db dyn Db) -> CallableType<'db> {
501-
Self::new(db, CallableSignature::bottom(), CallableTypeKind::Regular)
557+
Self::new(
558+
db,
559+
CallableSignature::bottom(),
560+
CallableTypeKind::Regular,
561+
CallableFunctionProvenance::None,
562+
)
502563
}
503564

504565
pub(super) fn recursive_type_normalized_impl(
@@ -512,6 +573,7 @@ impl<'db> CallableType<'db> {
512573
self.signatures(db)
513574
.recursive_type_normalized_impl(db, div, nested)?,
514575
self.kind(db),
576+
self.provenance(db),
515577
))
516578
}
517579

@@ -531,6 +593,7 @@ impl<'db> CallableType<'db> {
531593
self.signatures(db)
532594
.apply_type_mapping_impl(db, type_mapping, tcx, visitor),
533595
self.kind(db),
596+
self.provenance(db),
534597
)
535598
}
536599

@@ -625,6 +688,7 @@ impl<'db> CallableTypes<'db> {
625688
db,
626689
CallableSignature::from_overloads(overloads),
627690
CallableTypeKind::Regular,
691+
CallableFunctionProvenance::None,
628692
)
629693
.into_precise_functools_partial_instance(db, wrapped)
630694
}

crates/ty_python_semantic/src/types/class.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use super::{
1919
};
2020
use super::{TypeVarVariance, display};
2121
use crate::place::{DefinedPlace, TypeOrigin};
22-
use crate::types::callable::CallableTypeKind;
22+
use crate::types::callable::{CallableFunctionProvenance, CallableTypeKind};
2323
use crate::types::constraints::{
2424
ConstraintSet, ConstraintSetBuilder, IteratorConstraintsExtension,
2525
};
@@ -1587,6 +1587,7 @@ impl<'db> ClassType<'db> {
15871587
db,
15881588
getitem_signature,
15891589
CallableTypeKind::FunctionLike,
1590+
CallableFunctionProvenance::None,
15901591
));
15911592
Member::definitely_declared(getitem_type)
15921593
})
@@ -1828,6 +1829,7 @@ impl<'db> ClassType<'db> {
18281829
db,
18291830
dunder_new_signature.bind_self(db, Some(instance_ty)),
18301831
CallableTypeKind::Regular,
1832+
CallableFunctionProvenance::None,
18311833
);
18321834

18331835
if returns_non_subclass {
@@ -1898,6 +1900,7 @@ impl<'db> ClassType<'db> {
18981900
db,
18991901
synthesized_dunder_init_signature,
19001902
CallableTypeKind::Regular,
1903+
CallableFunctionProvenance::None,
19011904
))
19021905
} else {
19031906
None

crates/ty_python_semantic/src/types/class/static_literal.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::{
2424
Signature, SpecialFormType, StaticMroError, SubclassOfType, Truthiness, Type, TypeContext,
2525
TypeMapping, TypeVarVariance, UnionBuilder, UnionType,
2626
call::{CallError, CallErrorKind},
27-
callable::CallableTypeKind,
27+
callable::{CallableFunctionProvenance, CallableTypeKind},
2828
class::{
2929
ClassMemberResult, CodeGeneratorKind, DisjointBase, DynamicTypedDictLiteral, Field,
3030
FieldKind, InstanceMemberResult, MetaclassError, MetaclassErrorKind, MethodDecorator,
@@ -966,6 +966,7 @@ impl<'db> StaticClassLiteral<'db> {
966966
db,
967967
callable_ty.signatures(db),
968968
CallableTypeKind::FunctionLike,
969+
callable_ty.provenance(db),
969970
)),
970971
Type::Union(union) => {
971972
union.map(db, |element| into_function_like_callable(db, *element))
@@ -1191,7 +1192,12 @@ impl<'db> StaticClassLiteral<'db> {
11911192
)
11921193
}),
11931194
);
1194-
CallableType::new(db, signatures, CallableTypeKind::FunctionLike)
1195+
CallableType::new(
1196+
db,
1197+
signatures,
1198+
CallableTypeKind::FunctionLike,
1199+
CallableFunctionProvenance::None,
1200+
)
11951201
});
11961202

11971203
return Some(synthesized_callables.into_type(db));
@@ -1601,6 +1607,7 @@ impl<'db> StaticClassLiteral<'db> {
16011607
db,
16021608
CallableSignature::from_overloads(overloads),
16031609
CallableTypeKind::FunctionLike,
1610+
CallableFunctionProvenance::None,
16041611
)))
16051612
}
16061613

crates/ty_python_semantic/src/types/class/typed_dict.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use ruff_python_stdlib::identifiers::is_identifier;
1010
use ruff_text_size::{Ranged, TextRange};
1111

1212
use crate::place::PlaceAndQualifiers;
13-
use crate::types::callable::CallableTypeKind;
13+
use crate::types::callable::{CallableFunctionProvenance, CallableTypeKind};
1414
use crate::types::generics::GenericContext;
1515
use crate::types::member::Member;
1616
use crate::types::mro::Mro;
@@ -153,6 +153,7 @@ fn synthesize_typed_dict_init<'db>(
153153
db,
154154
CallableSignature::from_overloads([map_overload, keyword_overload]),
155155
CallableTypeKind::FunctionLike,
156+
CallableFunctionProvenance::None,
156157
))
157158
}
158159

@@ -176,6 +177,7 @@ fn synthesize_typed_dict_getitem<'db>(
176177
db,
177178
CallableSignature::from_overloads(overloads),
178179
CallableTypeKind::FunctionLike,
180+
CallableFunctionProvenance::None,
179181
))
180182
}
181183

@@ -219,6 +221,7 @@ fn synthesize_typed_dict_setitem<'db>(
219221
db,
220222
CallableSignature::from_overloads(overloads),
221223
CallableTypeKind::FunctionLike,
224+
CallableFunctionProvenance::None,
222225
))
223226
}
224227

@@ -258,6 +261,7 @@ fn synthesize_typed_dict_delitem<'db>(
258261
db,
259262
CallableSignature::from_overloads(overloads),
260263
CallableTypeKind::FunctionLike,
264+
CallableFunctionProvenance::None,
261265
))
262266
}
263267

@@ -377,6 +381,7 @@ fn synthesize_typed_dict_get<'db>(
377381
db,
378382
CallableSignature::from_overloads(overloads),
379383
CallableTypeKind::FunctionLike,
384+
CallableFunctionProvenance::None,
380385
))
381386
}
382387

@@ -490,6 +495,7 @@ fn synthesize_typed_dict_pop<'db>(
490495
db,
491496
CallableSignature::from_overloads(overloads),
492497
CallableTypeKind::FunctionLike,
498+
CallableFunctionProvenance::None,
493499
))
494500
}
495501

@@ -516,6 +522,7 @@ fn synthesize_typed_dict_setdefault<'db>(
516522
db,
517523
CallableSignature::from_overloads(overloads),
518524
CallableTypeKind::FunctionLike,
525+
CallableFunctionProvenance::None,
519526
))
520527
}
521528

@@ -583,6 +590,7 @@ fn synthesize_typed_dict_merge<'db>(
583590
db,
584591
CallableSignature::from_overloads(overloads),
585592
CallableTypeKind::FunctionLike,
593+
CallableFunctionProvenance::None,
586594
))
587595
}
588596

0 commit comments

Comments
 (0)