// InheritedMemberTests.swift // Covers Phase E2's global (cross-module) inherited-member post-pass in // `planModules`: a class in one Swift module that redeclares a same-name, // same-arity method already provided by an ancestor class in a DIFFERENT // module (e.g. `Pango.Coverage.ref()`/`unref()` vs. `GObject.Object.ref()`/ // `unref()`) must have its own copy dropped — Swift rejects the redundant // redeclaration across module boundaries. A same-name method with a // DIFFERENT arity (legitimate overload, e.g. `MemoryOutputStream.getData()` // vs. inherited `Object.getData(key:)`) must NOT be dropped. import Foundation import Testing @testable import GObjectGeneratorCore @Suite("Cross-module inherited-member dedup") struct InheritedMemberTests { /// Builds a two-module `MultiPackageAnalysis` + `TypeRegistry`: module A /// declares root class `Object` with `ref()` (no args) and `getData(key:)` /// (one arg); module B declares `Coverage: Object` (redeclaring `ref()` /// with the SAME arity) and `Stream: Object` (redeclaring `getData()` /// with a DIFFERENT arity — a legitimate overload). private func planTwoModules() throws -> [String: ModulePlan] { let tmpDir = NSTemporaryDirectory() + "inherited_member_test_\(UUID().uuidString)" try FileManager.default.createDirectory(atPath: tmpDir, withIntermediateDirectories: true) let moduleAGIR = """ """ let moduleAPath = tmpDir + "/AMod-1.0.gir" try moduleAGIR.write(toFile: moduleAPath, atomically: true, encoding: .utf8) let moduleBGIR = """ """ let moduleBPath = tmpDir + "/BMod-1.0.gir" try moduleBGIR.write(toFile: moduleBPath, atomically: true, encoding: .utf8) let packages: [PackageEntry] = [ PackageEntry(name: "AMod", girPath: moduleAPath), PackageEntry(name: "BMod", girPath: moduleBPath), ] let config = MonorepoConfig(outputDir: tmpDir, packages: packages) let analysis = try MultiPackageAnalyzer(config: config).analyze() let registry = TypeRegistry(repositories: analysis.repositories) return planModules(analysis: analysis, registry: registry) } @Test("A same-arity method redeclared across a cross-module inheritance edge is dropped") func crossModuleSameArityCollisionIsDropped() throws { let plans = try planTwoModules() guard let bPlan = plans["BMod"], case .class(let coverage)? = bPlan.types.first(where: { if case .class(let c) = $0 { return c.name == "Coverage" } return false }) else { Issue.record("expected BMod.Coverage to plan"); return } #expect(!coverage.methods.contains { $0.name == "ref" }) #expect(bPlan.skips.contains { $0.reason == .inheritedMember && $0.symbol.contains("Coverage.ref") }) } @Test("A different-arity method with the same base name across modules is kept (legitimate overload)") func crossModuleDifferentArityOverloadIsKept() throws { let plans = try planTwoModules() guard let bPlan = plans["BMod"], case .class(let stream)? = bPlan.types.first(where: { if case .class(let c) = $0 { return c.name == "Stream" } return false }) else { Issue.record("expected BMod.Stream to plan"); return } // Stream.getData() (0 args) must survive despite Object.getData(key:) // (1 arg) sharing the base name — different arity, not a collision. #expect(stream.methods.contains { $0.name == "getData" }) #expect(!bPlan.skips.contains { $0.reason == .inheritedMember && $0.symbol.contains("Stream.getData") }) } }