Skip to content

Commit a5e3d3e

Browse files
committed
promote function literals to callable types
1 parent 228ae69 commit a5e3d3e

File tree

6 files changed

+84
-39
lines changed

6 files changed

+84
-39
lines changed

crates/ty_python_semantic/resources/mdtest/literal/collections/list.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@ reveal_type([]) # revealed: list[Unknown]
1212
reveal_type([(1, 2), (3, 4)]) # revealed: list[Unknown | tuple[int, int]]
1313
```
1414

15+
## List of functions
16+
17+
```py
18+
def a(_: int) -> int:
19+
return 0
20+
21+
def b(_: int) -> int:
22+
return 1
23+
24+
x = [a, b]
25+
reveal_type(x) # revealed: list[Unknown | ((_: int) -> int)]
26+
```
27+
1528
## Mixed list
1629

1730
```py

crates/ty_python_semantic/resources/mdtest/literal/collections/set.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@ reveal_type({1, 2}) # revealed: set[Unknown | int]
1212
reveal_type({(1, 2), (3, 4)}) # revealed: set[Unknown | tuple[int, int]]
1313
```
1414

15+
## Set of functions
16+
17+
```py
18+
def a(_: int) -> int:
19+
return 0
20+
21+
def b(_: int) -> int:
22+
return 1
23+
24+
x = {a, b}
25+
reveal_type(x) # revealed: set[Unknown | ((_: int) -> int)]
26+
```
27+
1528
## Mixed set
1629

1730
```py

crates/ty_python_semantic/src/types.rs

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,24 +1133,21 @@ impl<'db> Type<'db> {
11331133
}
11341134
}
11351135

1136-
/// Promotes any literals within this instance type, or returns the type unchanged if there are
1137-
/// no literals to be promoted.
1138-
pub(crate) fn promote_literals(self, db: &'db dyn Db) -> Type<'db> {
1136+
/// If this type is a literal, returns a type that would be appropriate as a type annotation for
1137+
/// an instance of the literal.
1138+
///
1139+
/// Notably, this converts `def _() -> int` to `Callable[[], int]`, which `literal_fallback_instance`
1140+
/// does not.
1141+
pub(crate) fn literal_annotation_type(self, db: &'db dyn Db) -> Option<Type<'db>> {
11391142
match self {
1140-
// Promote literals within generic aliases, e.g. `tuple[Literal[1]]` to `tuple[int]`.
1141-
Type::NominalInstance(instance) => match instance.class(db).into_generic_alias() {
1142-
Some(alias) => {
1143-
let alias = alias.apply_type_mapping_impl(
1144-
db,
1145-
&TypeMapping::PromoteLiterals,
1146-
&ApplyTypeMappingVisitor::default(),
1147-
);
1148-
1149-
Type::instance(db, ClassType::Generic(alias))
1150-
}
1151-
_ => self,
1152-
},
1153-
_ => self.literal_fallback_instance(db).unwrap_or(self),
1143+
Type::StringLiteral(_) | Type::LiteralString => Some(KnownClass::Str.to_instance(db)),
1144+
Type::BooleanLiteral(_) => Some(KnownClass::Bool.to_instance(db)),
1145+
Type::IntLiteral(_) => Some(KnownClass::Int.to_instance(db)),
1146+
Type::BytesLiteral(_) => Some(KnownClass::Bytes.to_instance(db)),
1147+
Type::ModuleLiteral(_) => Some(KnownClass::ModuleType.to_instance(db)),
1148+
Type::EnumLiteral(literal) => Some(literal.enum_class_instance(db)),
1149+
Type::FunctionLiteral(literal) => Some(Type::Callable(literal.into_callable_type(db))),
1150+
_ => None,
11541151
}
11551152
}
11561153

@@ -6005,8 +6002,10 @@ impl<'db> Type<'db> {
60056002
self
60066003
}
60076004
}
6008-
TypeMapping::PromoteLiterals | TypeMapping::BindLegacyTypevars(_) |
6009-
TypeMapping::MarkTypeVarsInferable(_) => self,
6005+
TypeMapping::LiteralToInstance
6006+
| TypeMapping::LiteralToAnnotation
6007+
| TypeMapping::BindLegacyTypevars(_)
6008+
| TypeMapping::MarkTypeVarsInferable(_) => self,
60106009
TypeMapping::Materialize(materialization_kind) => {
60116010
Type::TypeVar(bound_typevar.materialize_impl(db, *materialization_kind, visitor))
60126011
}
@@ -6026,10 +6025,11 @@ impl<'db> Type<'db> {
60266025
self
60276026
}
60286027
}
6029-
TypeMapping::PromoteLiterals |
6030-
TypeMapping::BindLegacyTypevars(_) |
6031-
TypeMapping::BindSelf(_) |
6032-
TypeMapping::ReplaceSelf { .. }
6028+
TypeMapping::LiteralToInstance
6029+
| TypeMapping::LiteralToAnnotation
6030+
| TypeMapping::BindLegacyTypevars(_)
6031+
| TypeMapping::BindSelf(_)
6032+
| TypeMapping::ReplaceSelf { .. }
60336033
=> self,
60346034
TypeMapping::Materialize(materialization_kind) => Type::NonInferableTypeVar(bound_typevar.materialize_impl(db, *materialization_kind, visitor))
60356035

@@ -6041,15 +6041,22 @@ impl<'db> Type<'db> {
60416041
}
60426042
TypeMapping::Specialization(_) |
60436043
TypeMapping::PartialSpecialization(_) |
6044-
TypeMapping::PromoteLiterals |
6044+
TypeMapping::LiteralToInstance |
6045+
TypeMapping::LiteralToAnnotation |
60456046
TypeMapping::BindSelf(_) |
60466047
TypeMapping::ReplaceSelf { .. } |
60476048
TypeMapping::MarkTypeVarsInferable(_) |
60486049
TypeMapping::Materialize(_) => self,
60496050
}
60506051

60516052
Type::FunctionLiteral(function) => {
6052-
Type::FunctionLiteral(function.with_type_mapping(db, type_mapping))
6053+
let function = Type::FunctionLiteral(function.with_type_mapping(db, type_mapping));
6054+
6055+
match type_mapping {
6056+
TypeMapping::LiteralToAnnotation => function.literal_annotation_type(db)
6057+
.expect("function literal should have an annotation type"),
6058+
_ => function
6059+
}
60536060
}
60546061

60556062
Type::BoundMethod(method) => Type::BoundMethod(BoundMethodType::new(
@@ -6155,8 +6162,10 @@ impl<'db> Type<'db> {
61556162
TypeMapping::ReplaceSelf { .. } |
61566163
TypeMapping::MarkTypeVarsInferable(_) |
61576164
TypeMapping::Materialize(_) => self,
6158-
TypeMapping::PromoteLiterals => self.literal_fallback_instance(db)
6165+
TypeMapping::LiteralToInstance => self.literal_fallback_instance(db)
61596166
.expect("literal type should have fallback instance type"),
6167+
TypeMapping::LiteralToAnnotation => self.literal_annotation_type(db)
6168+
.expect("literal type should have an annotation type"),
61606169
}
61616170

61626171
Type::Dynamic(_) => match type_mapping {
@@ -6166,7 +6175,8 @@ impl<'db> Type<'db> {
61666175
TypeMapping::BindSelf(_) |
61676176
TypeMapping::ReplaceSelf { .. } |
61686177
TypeMapping::MarkTypeVarsInferable(_) |
6169-
TypeMapping::PromoteLiterals => self,
6178+
TypeMapping::LiteralToInstance => self,
6179+
TypeMapping::LiteralToAnnotation => self,
61706180
TypeMapping::Materialize(materialization_kind) => match materialization_kind {
61716181
MaterializationKind::Top => Type::object(),
61726182
MaterializationKind::Bottom => Type::Never,
@@ -6694,7 +6704,10 @@ pub enum TypeMapping<'a, 'db> {
66946704
PartialSpecialization(PartialSpecialization<'a, 'db>),
66956705
/// Promotes any literal types to their corresponding instance types (e.g. `Literal["string"]`
66966706
/// to `str`)
6697-
PromoteLiterals,
6707+
LiteralToInstance,
6708+
/// Promotes any literal types to their corresponding type annotation form (e.g. `Literal["string"]`
6709+
/// to `str`, or `def _() -> int` to `Callable[[], int]`).
6710+
LiteralToAnnotation,
66986711
/// Binds a legacy typevar with the generic context (class, function, type alias) that it is
66996712
/// being used in.
67006713
BindLegacyTypevars(BindingContext<'db>),
@@ -6726,7 +6739,8 @@ fn walk_type_mapping<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
67266739
TypeMapping::ReplaceSelf { new_upper_bound } => {
67276740
visitor.visit_type(db, *new_upper_bound);
67286741
}
6729-
TypeMapping::PromoteLiterals
6742+
TypeMapping::LiteralToInstance
6743+
| TypeMapping::LiteralToAnnotation
67306744
| TypeMapping::BindLegacyTypevars(_)
67316745
| TypeMapping::MarkTypeVarsInferable(_)
67326746
| TypeMapping::Materialize(_) => {}
@@ -6742,7 +6756,8 @@ impl<'db> TypeMapping<'_, 'db> {
67426756
TypeMapping::PartialSpecialization(partial) => {
67436757
TypeMapping::PartialSpecialization(partial.to_owned())
67446758
}
6745-
TypeMapping::PromoteLiterals => TypeMapping::PromoteLiterals,
6759+
TypeMapping::LiteralToInstance => TypeMapping::LiteralToInstance,
6760+
TypeMapping::LiteralToAnnotation => TypeMapping::LiteralToAnnotation,
67466761
TypeMapping::BindLegacyTypevars(binding_context) => {
67476762
TypeMapping::BindLegacyTypevars(*binding_context)
67486763
}
@@ -6767,7 +6782,8 @@ impl<'db> TypeMapping<'_, 'db> {
67676782
TypeMapping::PartialSpecialization(partial) => {
67686783
TypeMapping::PartialSpecialization(partial.normalized_impl(db, visitor))
67696784
}
6770-
TypeMapping::PromoteLiterals => TypeMapping::PromoteLiterals,
6785+
TypeMapping::LiteralToInstance => TypeMapping::LiteralToInstance,
6786+
TypeMapping::LiteralToAnnotation => TypeMapping::LiteralToAnnotation,
67716787
TypeMapping::BindLegacyTypevars(binding_context) => {
67726788
TypeMapping::BindLegacyTypevars(*binding_context)
67736789
}
@@ -6795,7 +6811,8 @@ impl<'db> TypeMapping<'_, 'db> {
67956811
match self {
67966812
TypeMapping::Specialization(_)
67976813
| TypeMapping::PartialSpecialization(_)
6798-
| TypeMapping::PromoteLiterals
6814+
| TypeMapping::LiteralToInstance
6815+
| TypeMapping::LiteralToAnnotation
67996816
| TypeMapping::BindLegacyTypevars(_)
68006817
| TypeMapping::MarkTypeVarsInferable(_)
68016818
| TypeMapping::Materialize(_) => context,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2277,7 +2277,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
22772277
// inferred as a literal to the corresponding instance type.
22782278
builder
22792279
.build(gc)
2280-
.apply_type_mapping(self.db, &TypeMapping::PromoteLiterals)
2280+
.apply_type_mapping(self.db, &TypeMapping::LiteralToInstance)
22812281
});
22822282
}
22832283

crates/ty_python_semantic/src/types/diagnostic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2626,7 +2626,7 @@ pub(crate) fn report_undeclared_protocol_member(
26262626
let binding_type = binding_type(db, definition);
26272627

26282628
let suggestion = binding_type
2629-
.literal_fallback_instance(db)
2629+
.literal_annotation_type(db)
26302630
.unwrap_or(binding_type);
26312631

26322632
if should_give_hint(db, suggestion) {

crates/ty_python_semantic/src/types/infer/builder.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,9 @@ use crate::types::{
8989
IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType, MemberLookupPolicy,
9090
MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType,
9191
SubclassOfType, TrackedConstraintSet, Truthiness, Type, TypeAliasType, TypeAndQualifiers,
92-
TypeContext, TypeQualifiers, TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation,
93-
TypeVarInstance, TypeVarKind, UnionBuilder, UnionType, binding_type, todo_type,
92+
TypeContext, TypeMapping, TypeQualifiers, TypeVarBoundOrConstraintsEvaluation,
93+
TypeVarDefaultEvaluation, TypeVarInstance, TypeVarKind, UnionBuilder, UnionType, binding_type,
94+
todo_type,
9495
};
9596
use crate::types::{ClassBase, add_inferred_python_version_hint_to_diagnostic};
9697
use crate::unpack::{EvaluationMode, UnpackPosition};
@@ -5366,9 +5367,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
53665367

53675368
// The inferred type of each element acts as an additional constraint on `T`.
53685369
for inferred_elt_ty in inferred_elt_tys {
5369-
// Promote element literals to their fallback instance type to avoid excessively large unions
5370-
// for large nested list literals, which the constraint solver struggles with.
5371-
let inferred_elt_ty = inferred_elt_ty.promote_literals(self.db());
5370+
// Promote element literals to their type annotation form to avoid excessively large
5371+
// unions for large nested list literals, which the constraint solver struggles with.
5372+
let inferred_elt_ty =
5373+
inferred_elt_ty.apply_type_mapping(self.db(), &TypeMapping::LiteralToAnnotation);
53725374
builder.infer(elts_ty, inferred_elt_ty).ok()?;
53735375
}
53745376

0 commit comments

Comments
 (0)