|
9 | 9 |
|
10 | 10 | use crate::Db; |
11 | 11 | use ty_module_resolver::KnownModule; |
12 | | -use ty_python_core::use_def_map; |
| 12 | +use ty_python_core::{definition::Definition, use_def_map}; |
13 | 13 |
|
14 | | -use super::call::CallArguments; |
| 14 | +use super::call::{CallArguments, CallDunderError}; |
15 | 15 | use super::callable::CallableTypeKind; |
| 16 | +use super::constraints::{ConstraintSet, IteratorConstraintsExtension, OptionConstraintsExtension}; |
| 17 | +use super::relation::TypeRelationChecker; |
16 | 18 | 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, |
19 | 22 | }; |
20 | 23 | use crate::ProgramEnvironment; |
21 | 24 | use crate::place::{ |
@@ -1059,3 +1062,372 @@ fn contains_signature_typevar<'db>( |
1059 | 1062 | }) |
1060 | 1063 | }) |
1061 | 1064 | } |
| 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