Skip to content

Commit 56bde8f

Browse files
committed
[ty] Share attribute write compatibility checks
1 parent 15f3fe6 commit 56bde8f

2 files changed

Lines changed: 384 additions & 361 deletions

File tree

‎crates/ty_python_semantic/src/types/attribute_write.rs‎

Lines changed: 376 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@
99
1010
use crate::Db;
1111
use ty_module_resolver::KnownModule;
12-
use ty_python_core::use_def_map;
12+
use ty_python_core::{definition::Definition, use_def_map};
1313

14-
use super::call::CallArguments;
14+
use super::call::{CallArguments, CallDunderError};
1515
use super::callable::CallableTypeKind;
16+
use super::constraints::{ConstraintSet, IteratorConstraintsExtension, OptionConstraintsExtension};
17+
use super::relation::TypeRelationChecker;
1618
use super::{
17-
IntersectionType, KnownClass, KnownInstanceType, MemberLookupPolicy, Parameter, Signature,
18-
Type, TypeQualifiers, TypeVarBoundOrConstraints, UnionType,
19+
BindingContext, IntersectionType, KnownClass, KnownInstanceType, MemberLookupPolicy, Parameter,
20+
PropertyInstanceType, SelfBinding, Signature, Type, TypeContext, TypeMapping, TypeQualifiers,
21+
TypeVarBoundOrConstraints, UnionType, UpcastPolicy,
1922
};
2023
use crate::ProgramEnvironment;
2124
use crate::place::{
@@ -1059,3 +1062,372 @@ fn contains_signature_typevar<'db>(
10591062
})
10601063
})
10611064
}
1065+
1066+
/// Returns a property's accepted value type and the definition that binds its `Self`.
1067+
pub(super) fn property_setter_value_type<'db>(
1068+
db: &'db dyn Db,
1069+
env: &ProgramEnvironment<'db>,
1070+
setter: Type<'db>,
1071+
) -> Option<(Type<'db>, Option<Definition<'db>>)> {
1072+
let mut set_types = Vec::new();
1073+
let mut definition = None;
1074+
for callable in &setter.try_upcast_to_callable(db, env)? {
1075+
for signature in callable.signatures(db) {
1076+
set_types.push(signature.parameters().get_positional(1)?.annotated_type());
1077+
definition = definition.or(signature.definition());
1078+
}
1079+
}
1080+
Some((UnionType::from_elements(db, env, set_types), definition))
1081+
}
1082+
1083+
fn property_set_type<'db>(
1084+
db: &'db dyn Db,
1085+
env: &ProgramEnvironment<'db>,
1086+
property: PropertyInstanceType<'db>,
1087+
receiver_ty: Type<'db>,
1088+
) -> Option<Type<'db>> {
1089+
let (ty, definition) = property_setter_value_type(db, env, property.setter(db)?)?;
1090+
if !ty.contains_self(db, env) {
1091+
return Some(ty);
1092+
}
1093+
Some(ty.apply_type_mapping(
1094+
db,
1095+
env,
1096+
&TypeMapping::BindSelf(SelfBinding::new(
1097+
db,
1098+
env,
1099+
receiver_ty,
1100+
definition.map(BindingContext::Definition),
1101+
)),
1102+
TypeContext::default(),
1103+
))
1104+
}
1105+
1106+
impl<'c, 'db> TypeRelationChecker<'_, 'c, 'db> {
1107+
/// Checks a required attribute write using normal attribute-assignment lookup.
1108+
///
1109+
/// Resolution is shared with real assignments, but this path evaluates the result using the
1110+
/// active type relation and constraints instead of inferring an expression or emitting an
1111+
/// assignment diagnostic.
1112+
pub(super) fn check_attribute_write(
1113+
&self,
1114+
db: &'db dyn Db,
1115+
ty: Type<'db>,
1116+
member_name: &str,
1117+
value_ty: Type<'db>,
1118+
) -> ConstraintSet<'db, 'c> {
1119+
let env = self.env;
1120+
let requirement = attribute_write_requirement(db, env, ty, member_name);
1121+
self.check_attribute_write_requirement(db, &requirement, member_name, value_ty)
1122+
}
1123+
1124+
fn check_attribute_write_requirement(
1125+
&self,
1126+
db: &'db dyn Db,
1127+
requirement: &AttributeWriteRequirement<'db>,
1128+
member_name: &str,
1129+
value_ty: Type<'db>,
1130+
) -> ConstraintSet<'db, 'c> {
1131+
match requirement {
1132+
AttributeWriteRequirement::All { element_tys, .. } => {
1133+
let env = self.env;
1134+
let mut result = self.always();
1135+
for element_ty in *element_tys {
1136+
let requirement =
1137+
attribute_write_requirement(db, env, *element_ty, member_name);
1138+
let element_result = self.check_attribute_write_requirement(
1139+
db,
1140+
&requirement,
1141+
member_name,
1142+
value_ty,
1143+
);
1144+
result = result.and(db, self.constraints, || element_result);
1145+
if result.is_trivially_never_satisfied() {
1146+
break;
1147+
}
1148+
}
1149+
result
1150+
}
1151+
AttributeWriteRequirement::Any { intersection, .. } => {
1152+
let env = self.env;
1153+
let mut result = self.never();
1154+
for element_ty in intersection.positive(db) {
1155+
let requirement =
1156+
attribute_write_requirement(db, env, *element_ty, member_name);
1157+
let element_result = self.check_attribute_write_requirement(
1158+
db,
1159+
&requirement,
1160+
member_name,
1161+
value_ty,
1162+
);
1163+
result = result.or(db, self.constraints, || element_result);
1164+
if result.is_trivially_always_satisfied() {
1165+
break;
1166+
}
1167+
}
1168+
result
1169+
}
1170+
AttributeWriteRequirement::Unconstrained => self.always(),
1171+
AttributeWriteRequirement::CannotAssign => self.never(),
1172+
AttributeWriteRequirement::Module(Some(write_ty)) => {
1173+
self.check_type_pair(db, value_ty, *write_ty)
1174+
}
1175+
AttributeWriteRequirement::ProtocolMember {
1176+
write: Some(ProtocolMemberWriteRequirement::AssignableTo(write_ty)),
1177+
..
1178+
} => self.check_type_pair(db, value_ty, *write_ty),
1179+
AttributeWriteRequirement::ProtocolMember {
1180+
write: Some(ProtocolMemberWriteRequirement::Descriptor { domain, .. }),
1181+
..
1182+
} => self.check_type_pair(db, value_ty, domain.unwrap_or_else(Type::unknown)),
1183+
AttributeWriteRequirement::Module(None)
1184+
| AttributeWriteRequirement::ProtocolMember { write: None, .. } => self.never(),
1185+
AttributeWriteRequirement::Instance { object_ty, member } => {
1186+
self.check_instance_attribute_write(db, *object_ty, member, member_name, value_ty)
1187+
}
1188+
AttributeWriteRequirement::Class { object_ty, member } => {
1189+
self.check_class_attribute_write(db, *object_ty, member, value_ty)
1190+
}
1191+
}
1192+
}
1193+
1194+
fn check_instance_attribute_write(
1195+
&self,
1196+
db: &'db dyn Db,
1197+
object_ty: Type<'db>,
1198+
member: &InstanceAttributeWriteMember<'db>,
1199+
member_name: &str,
1200+
value_ty: Type<'db>,
1201+
) -> ConstraintSet<'db, 'c> {
1202+
let env = self.env;
1203+
let setattr_result = object_ty.try_call_dunder_with_policy(
1204+
db,
1205+
env,
1206+
"__setattr__",
1207+
&mut CallArguments::positional([Type::string_literal(db, member_name), value_ty]),
1208+
TypeContext::default(),
1209+
MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK,
1210+
);
1211+
if match &setattr_result {
1212+
Ok(bindings) => bindings.return_type(db, env).is_never(),
1213+
Err(error) => error.return_type(db, env).is_some_and(|ty| ty.is_never()),
1214+
} {
1215+
return self.never();
1216+
}
1217+
1218+
match member {
1219+
InstanceAttributeWriteMember::ClassVar => self.never(),
1220+
InstanceAttributeWriteMember::Explicit { member, fallback } => {
1221+
let member_result =
1222+
self.check_explicit_attribute_write(db, object_ty, member, value_ty);
1223+
if let Some(fallback) = fallback {
1224+
let fallback_result =
1225+
self.check_fallback_attribute_write(db, fallback, value_ty);
1226+
member_result.and(db, self.constraints, || fallback_result)
1227+
} else {
1228+
member_result
1229+
}
1230+
}
1231+
InstanceAttributeWriteMember::Instance(fallback) => {
1232+
self.check_fallback_attribute_write(db, fallback, value_ty)
1233+
}
1234+
InstanceAttributeWriteMember::SetAttr => {
1235+
if !matches!(
1236+
setattr_result,
1237+
Ok(_) | Err(CallDunderError::PossiblyUnbound { .. })
1238+
) {
1239+
return self.never();
1240+
}
1241+
self.check_setattr_attribute_write(db, object_ty, value_ty)
1242+
}
1243+
}
1244+
}
1245+
1246+
fn check_class_attribute_write(
1247+
&self,
1248+
db: &'db dyn Db,
1249+
object_ty: Type<'db>,
1250+
member: &ClassAttributeWriteMember<'db>,
1251+
value_ty: Type<'db>,
1252+
) -> ConstraintSet<'db, 'c> {
1253+
match member {
1254+
ClassAttributeWriteMember::Explicit { member, fallback } => {
1255+
let member_result =
1256+
self.check_explicit_attribute_write(db, object_ty, member, value_ty);
1257+
if member_result.is_trivially_never_satisfied() {
1258+
return member_result;
1259+
}
1260+
if let Some(fallback) = fallback {
1261+
let fallback_result =
1262+
self.check_fallback_attribute_write(db, fallback, value_ty);
1263+
member_result.and(db, self.constraints, || fallback_result)
1264+
} else {
1265+
member_result
1266+
}
1267+
}
1268+
ClassAttributeWriteMember::ClassAttribute(fallback) => {
1269+
self.check_fallback_attribute_write(db, fallback, value_ty)
1270+
}
1271+
ClassAttributeWriteMember::Unresolved { .. } => self.never(),
1272+
}
1273+
}
1274+
1275+
fn check_explicit_attribute_write(
1276+
&self,
1277+
db: &'db dyn Db,
1278+
object_ty: Type<'db>,
1279+
requirement: &ExplicitAttributeWriteRequirement<'db>,
1280+
value_ty: Type<'db>,
1281+
) -> ConstraintSet<'db, 'c> {
1282+
if requirement.qualifiers().contains(TypeQualifiers::FINAL) {
1283+
return self.never();
1284+
}
1285+
match requirement {
1286+
ExplicitAttributeWriteRequirement::Descriptor { descriptor_ty, .. } => {
1287+
if let Some(property) = descriptor_ty.as_property_instance()
1288+
&& let Some(set_type) = property_set_type(db, self.env, property, object_ty)
1289+
{
1290+
return self.check_type_pair(db, value_ty, set_type);
1291+
}
1292+
self.check_descriptor_attribute_write(db, *descriptor_ty, object_ty, value_ty)
1293+
}
1294+
ExplicitAttributeWriteRequirement::AssignableTo { ty, .. } => {
1295+
self.check_type_pair(db, value_ty, *ty)
1296+
}
1297+
}
1298+
}
1299+
1300+
fn check_descriptor_attribute_write(
1301+
&self,
1302+
db: &'db dyn Db,
1303+
descriptor_ty: Type<'db>,
1304+
object_ty: Type<'db>,
1305+
value_ty: Type<'db>,
1306+
) -> ConstraintSet<'db, 'c> {
1307+
let env = self.env;
1308+
let descriptor_ty = descriptor_ty.resolve_type_alias(db);
1309+
if let Type::Union(union) = descriptor_ty {
1310+
return union
1311+
.elements(db)
1312+
.iter()
1313+
.when_all(db, self.constraints, |descriptor_ty| {
1314+
self.check_descriptor_attribute_write(db, *descriptor_ty, object_ty, value_ty)
1315+
});
1316+
}
1317+
if matches!(
1318+
descriptor_ty.try_call_dunder_with_policy(
1319+
db,
1320+
env,
1321+
"__set__",
1322+
&mut CallArguments::positional([object_ty, Type::unknown()]),
1323+
TypeContext::default(),
1324+
MemberLookupPolicy::REQUIRE_CONCRETE,
1325+
),
1326+
Err(CallDunderError::CallError(..) | CallDunderError::MethodNotAvailable)
1327+
) {
1328+
return self.never();
1329+
}
1330+
let Place::Defined(DefinedPlace { ty: setter_ty, .. }) =
1331+
descriptor_setter(db, env, descriptor_ty)
1332+
else {
1333+
return self.never();
1334+
};
1335+
1336+
self.check_callable_write_parameter(db, setter_ty, 1, descriptor_ty, value_ty)
1337+
}
1338+
1339+
fn check_setattr_attribute_write(
1340+
&self,
1341+
db: &'db dyn Db,
1342+
object_ty: Type<'db>,
1343+
value_ty: Type<'db>,
1344+
) -> ConstraintSet<'db, 'c> {
1345+
let env = self.env;
1346+
let Place::Defined(DefinedPlace { ty: setattr_ty, .. }) = object_ty
1347+
.member_lookup_with_policy(
1348+
db,
1349+
env,
1350+
"__setattr__",
1351+
MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK
1352+
| MemberLookupPolicy::NO_INSTANCE_FALLBACK,
1353+
)
1354+
.place
1355+
else {
1356+
return self.never();
1357+
};
1358+
1359+
self.check_callable_write_parameter(db, setattr_ty, 1, object_ty, value_ty)
1360+
}
1361+
1362+
fn check_callable_write_parameter(
1363+
&self,
1364+
db: &'db dyn Db,
1365+
callable_ty: Type<'db>,
1366+
parameter_index: usize,
1367+
self_ty: Type<'db>,
1368+
value_ty: Type<'db>,
1369+
) -> ConstraintSet<'db, 'c> {
1370+
if let Type::Union(union) = value_ty {
1371+
return union
1372+
.elements(db)
1373+
.iter()
1374+
.when_all(db, self.constraints, |value_ty| {
1375+
self.check_callable_write_parameter(
1376+
db,
1377+
callable_ty,
1378+
parameter_index,
1379+
self_ty,
1380+
*value_ty,
1381+
)
1382+
});
1383+
}
1384+
1385+
let env = self.env;
1386+
callable_ty
1387+
.try_upcast_to_callable_with_policy(db, env, UpcastPolicy::from(self.relation))
1388+
.when_some_and(db, self.constraints, |callables| {
1389+
callables.iter().when_all(db, self.constraints, |callable| {
1390+
callable.signatures(db).into_iter().when_any(
1391+
db,
1392+
self.constraints,
1393+
|signature| {
1394+
let parameters = signature.parameters();
1395+
parameters
1396+
.get_positional(parameter_index)
1397+
.or_else(|| {
1398+
parameters.variadic().and_then(|(index, parameter)| {
1399+
(index <= parameter_index).then_some(parameter)
1400+
})
1401+
})
1402+
.map(|parameter| {
1403+
parameter
1404+
.annotated_type()
1405+
.bind_self_typevars(db, env, self_ty)
1406+
})
1407+
.when_some_and(db, self.constraints, |write_ty| {
1408+
self.check_type_pair(db, value_ty, write_ty)
1409+
})
1410+
},
1411+
)
1412+
})
1413+
})
1414+
}
1415+
1416+
fn check_fallback_attribute_write(
1417+
&self,
1418+
db: &'db dyn Db,
1419+
requirement: &FallbackAttributeWriteRequirement<'db>,
1420+
value_ty: Type<'db>,
1421+
) -> ConstraintSet<'db, 'c> {
1422+
match requirement {
1423+
FallbackAttributeWriteRequirement::AssignableTo { ty, qualifiers, .. } => {
1424+
if qualifiers.contains(TypeQualifiers::FINAL) {
1425+
self.never()
1426+
} else {
1427+
self.check_type_pair(db, value_ty, *ty)
1428+
}
1429+
}
1430+
FallbackAttributeWriteRequirement::PossiblyMissing => self.always(),
1431+
}
1432+
}
1433+
}

0 commit comments

Comments
 (0)