|
1 | 1 | use super::*; |
2 | 2 | use crate::test_fixtures::{make_class, make_method, make_property}; |
3 | | -use crate::types::Visibility; |
| 3 | +use crate::types::{ClassLikeKind, ConstantInfo, Visibility}; |
4 | 4 | use std::sync::Arc; |
5 | 5 |
|
6 | 6 | // ── VirtualMembers tests ──────────────────────────────────────────── |
@@ -1186,3 +1186,90 @@ fn evict_cast_class_chains_through_model_to_child() { |
1186 | 1186 | "cast eviction should chain: cast → model (via casts) → child (via parent)" |
1187 | 1187 | ); |
1188 | 1188 | } |
| 1189 | + |
| 1190 | +// ── B12: interface-extends-interface transitive member merging ─────── |
| 1191 | + |
| 1192 | +#[test] |
| 1193 | +fn resolve_class_fully_merges_transitive_interface_constants() { |
| 1194 | + // Simulates the Carbon scenario: |
| 1195 | + // interface UnitValue { const JANUARY = 1; } |
| 1196 | + // interface JsonSerializable { function jsonSerialize(): mixed; } |
| 1197 | + // interface DateTimeInterface { function format(string $f): string; } |
| 1198 | + // interface CarbonInterface extends DateTimeInterface, JsonSerializable, UnitValue {} |
| 1199 | + // class Carbon implements CarbonInterface {} |
| 1200 | + // |
| 1201 | + // Before the fix, only DateTimeInterface (the first extended |
| 1202 | + // interface, stored in `parent_class`) was merged. Members from |
| 1203 | + // JsonSerializable and UnitValue were lost. |
| 1204 | + |
| 1205 | + let mut unit_value = make_class("Carbon\\Constants\\UnitValue"); |
| 1206 | + unit_value.kind = ClassLikeKind::Interface; |
| 1207 | + unit_value.constants.push(ConstantInfo { |
| 1208 | + name: "JANUARY".to_string(), |
| 1209 | + name_offset: 0, |
| 1210 | + type_hint: Some("int".to_string()), |
| 1211 | + visibility: Visibility::Public, |
| 1212 | + deprecation_message: None, |
| 1213 | + deprecated_replacement: None, |
| 1214 | + see_refs: Vec::new(), |
| 1215 | + description: None, |
| 1216 | + is_enum_case: false, |
| 1217 | + enum_value: None, |
| 1218 | + value: Some("1".to_string()), |
| 1219 | + is_virtual: false, |
| 1220 | + }); |
| 1221 | + |
| 1222 | + let mut json_serializable = make_class("JsonSerializable"); |
| 1223 | + json_serializable.kind = ClassLikeKind::Interface; |
| 1224 | + json_serializable |
| 1225 | + .methods |
| 1226 | + .push(make_method("jsonSerialize", Some("mixed"))); |
| 1227 | + |
| 1228 | + let mut datetime_iface = make_class("DateTimeInterface"); |
| 1229 | + datetime_iface.kind = ClassLikeKind::Interface; |
| 1230 | + datetime_iface |
| 1231 | + .methods |
| 1232 | + .push(make_method("format", Some("string"))); |
| 1233 | + |
| 1234 | + let mut carbon_iface = make_class("CarbonInterface"); |
| 1235 | + carbon_iface.kind = ClassLikeKind::Interface; |
| 1236 | + carbon_iface.parent_class = Some("DateTimeInterface".to_string()); |
| 1237 | + carbon_iface.interfaces = vec![ |
| 1238 | + "DateTimeInterface".to_string(), |
| 1239 | + "JsonSerializable".to_string(), |
| 1240 | + "Carbon\\Constants\\UnitValue".to_string(), |
| 1241 | + ]; |
| 1242 | + |
| 1243 | + let mut carbon = make_class("Carbon"); |
| 1244 | + carbon.interfaces = vec!["CarbonInterface".to_string()]; |
| 1245 | + |
| 1246 | + let class_loader = move |name: &str| -> Option<Arc<ClassInfo>> { |
| 1247 | + match name { |
| 1248 | + "CarbonInterface" => Some(Arc::new(carbon_iface.clone())), |
| 1249 | + "DateTimeInterface" => Some(Arc::new(datetime_iface.clone())), |
| 1250 | + "JsonSerializable" => Some(Arc::new(json_serializable.clone())), |
| 1251 | + "Carbon\\Constants\\UnitValue" => Some(Arc::new(unit_value.clone())), |
| 1252 | + _ => None, |
| 1253 | + } |
| 1254 | + }; |
| 1255 | + |
| 1256 | + let resolved = crate::virtual_members::resolve_class_fully(&carbon, &class_loader); |
| 1257 | + |
| 1258 | + // DateTimeInterface::format — merged via CarbonInterface's parent_class chain |
| 1259 | + assert!( |
| 1260 | + resolved.methods.iter().any(|m| m.name == "format"), |
| 1261 | + "should have DateTimeInterface::format" |
| 1262 | + ); |
| 1263 | + |
| 1264 | + // JsonSerializable::jsonSerialize — merged via transitive interface collection |
| 1265 | + assert!( |
| 1266 | + resolved.methods.iter().any(|m| m.name == "jsonSerialize"), |
| 1267 | + "should have JsonSerializable::jsonSerialize (2nd extended interface)" |
| 1268 | + ); |
| 1269 | + |
| 1270 | + // UnitValue::JANUARY — merged via transitive interface collection |
| 1271 | + assert!( |
| 1272 | + resolved.constants.iter().any(|c| c.name == "JANUARY"), |
| 1273 | + "should have UnitValue::JANUARY constant (3rd extended interface)" |
| 1274 | + ); |
| 1275 | +} |
0 commit comments