From 02b5933106be6f2b14cae73d684f6ad21fcc8e7d Mon Sep 17 00:00:00 2001 From: Yury Yurevich Date: Wed, 19 Feb 2025 11:04:53 -0800 Subject: [PATCH 1/5] Update to have more clearer test case. Before: the test scenario asserted two cases: that a missing dependency triggers error; that a similar name includes a suggested correction. After: the test scenario asserts one case: that a missing dependency triggers error. Motivation: similar names already have dedicated tests, cleaning this up to provide more clarity on intent of the tests. --- Tests/PackageGraphTests/ModulesGraphTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/PackageGraphTests/ModulesGraphTests.swift b/Tests/PackageGraphTests/ModulesGraphTests.swift index 11ce25959ef..69fb86d55db 100644 --- a/Tests/PackageGraphTests/ModulesGraphTests.swift +++ b/Tests/PackageGraphTests/ModulesGraphTests.swift @@ -2868,11 +2868,11 @@ final class ModulesGraphTests: XCTestCase { targets: [ TargetDescription( name: "aaa", - dependencies: ["zzy"], + dependencies: ["mmm"], type: .executable ) ]), - Manifest.createRootManifest( + Manifest.createFileSystemManifest( displayName: "zzz", path: "/zzz", products: [ @@ -2893,7 +2893,7 @@ final class ModulesGraphTests: XCTestCase { testDiagnostics(observability.diagnostics) { result in result.check( - diagnostic: "product 'zzy' required by package 'aaa' target 'aaa' not found. Did you mean 'zzz'?", + diagnostic: "product 'mmm' required by package 'aaa' target 'aaa' not found.", severity: .error ) } From 8bc53ef8cb04742587f08421ac10a76d3c1f93a9 Mon Sep 17 00:00:00 2001 From: Yury Yurevich Date: Wed, 19 Feb 2025 16:23:58 -0800 Subject: [PATCH 2/5] Refactor test for similar name resolution to disambiguate different scenarios. Refactor test and split into two to make it clear what scenario does it verify. Assert looks the same between the two tests, but the setup is different: - testByNameDependencyWithSimilarTargetName verifies typo in dependencies between modules within the same package. - testByNameDependencyWithSimilarProductName verifies typo in dependencies on a product from a different package. --- .../PackageGraphTests/ModulesGraphTests.swift | 66 +++++++++++++++---- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/Tests/PackageGraphTests/ModulesGraphTests.swift b/Tests/PackageGraphTests/ModulesGraphTests.swift index 69fb86d55db..c98d6862d7f 100644 --- a/Tests/PackageGraphTests/ModulesGraphTests.swift +++ b/Tests/PackageGraphTests/ModulesGraphTests.swift @@ -957,10 +957,11 @@ final class ModulesGraphTests: XCTestCase { } } - func testProductDependencyWithSimilarName() throws { - let fs = InMemoryFileSystem(emptyFiles: - "/Foo/Sources/Foo/foo.swift", - "/Bar/Sources/Bar/bar.swift" + func testByNameDependencyWithSimilarTargetName() throws { + let fs = InMemoryFileSystem( + emptyFiles: + "/railroad/Sources/Rail/Rail.swift", + "/railroad/Sources/Spike/Spike.swift" ) let observability = ObservabilitySystem.makeForTesting() @@ -968,29 +969,66 @@ final class ModulesGraphTests: XCTestCase { fileSystem: fs, manifests: [ Manifest.createRootManifest( - displayName: "Foo", - path: "/Foo", + displayName: "railroad", + path: "/railroad", targets: [ - TargetDescription(name: "Foo", dependencies: ["Barx"]), - ]), + TargetDescription(name: "Rail", dependencies: ["Spoke"]), + TargetDescription(name: "Spike"), + ] + ), + ], + observabilityScope: observability.topScope + ) + + testDiagnostics(observability.diagnostics) { result in + result.check( + diagnostic: "product 'Spoke' required by package 'railroad' target 'Rail' not found. Did you mean 'Spike'?", + severity: .error + ) + } + } + + func testByNameDependencyWithSimilarProductName() throws { + let fs = InMemoryFileSystem( + emptyFiles: + "/weather/Sources/Rain/Rain.swift", + "/forecast/Sources/Forecast/Forecast.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + _ = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createFileSystemManifest( + displayName: "weather", + path: "/weather", + products: [ + ProductDescription(name: "Rain", type: .library(.automatic), targets: ["Rain"]) + ], + targets: [ + TargetDescription(name: "Rain"), + ] + ), Manifest.createRootManifest( - displayName: "Bar", - path: "/Bar", + displayName: "forecast", + path: "/forecast", + dependencies: [.fileSystem(path: "/weather")], targets: [ - TargetDescription(name: "Bar") - ]), + TargetDescription(name: "Forecast", dependencies: ["Rail"]), + ] + ), ], observabilityScope: observability.topScope ) testDiagnostics(observability.diagnostics) { result in result.check( - diagnostic: "product 'Barx' required by package 'foo' target 'Foo' not found. Did you mean 'Bar'?", + diagnostic: "product 'Rail' required by package 'forecast' target 'Forecast' not found. Did you mean 'Rain'?", severity: .error ) } } - + func testProductDependencyWithNonSimilarName() throws { let fs = InMemoryFileSystem(emptyFiles: "/Foo/Sources/Foo/foo.swift", From d71eb5a2bb086944f9ec8db54254130d5bbfc303 Mon Sep 17 00:00:00 2001 From: Yury Yurevich Date: Wed, 19 Feb 2025 19:25:09 -0800 Subject: [PATCH 3/5] Update implementation of suggestions for typos in dependencies. Before: occasionally swiftpm suggested dependencies which aren't accurate, since it looked for global list of modules for matches, while actual depedencies are on products. After: swiftpm suggestions now search through - local modules - global products - package-specific products if explicit package is used in the deps Motivation: inaccurate suggestions which made me distrust swiftpm. --- .../PackageGraph/ModulesGraph+Loading.swift | 58 ++++++- .../PackageGraphTests/ModulesGraphTests.swift | 149 +++++++++++++++++- 2 files changed, 198 insertions(+), 9 deletions(-) diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index a95c87266c1..58ba3a8ae47 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -589,6 +589,13 @@ private func createResolvedPackages( // The set of all module names. var allModuleNames = Set() + // The set of all product names across all packages in the dependency tree. + var allProductNames = Set() + // The set of product names for each package in the dependency tree. + var productNamesForPackage: [String: Set] = [:] + // Reverse lookup: package name or identity for each product. + var packageIdForProduct: [String: String] = [:] + // Track if multiple modules are found with the same name. var foundDuplicateModule = false @@ -630,6 +637,13 @@ private func createResolvedPackages( let manifestProducts = dependency.package.manifest.products.lazy.map { $0.name } let explicitProducts = dependency.package.products.filter { manifestProducts.contains($0.name) } let explicitIdsOrNames = Set(explicitProducts.lazy.map({ lookupByProductIDs ? $0.identity : $0.name })) + let explicitNames = Set(explicitProducts.map { $0.name }) + allProductNames.formUnion(explicitNames) + productNamesForPackage[dependency.package.identity.description] = explicitNames + packageIdForProduct.merge( + explicitNames.map { ($0, dependency.package.identity.description) }, + uniquingKeysWith: { _, new in new } + ) return dependency.products.filter({ lookupByProductIDs ? explicitIdsOrNames.contains($0.product.identity) : explicitIdsOrNames.contains($0.product.name) }) }) @@ -692,14 +706,44 @@ private func createResolvedPackages( t.name != productRef.name } - // Find a product name from the available product dependencies that is most similar to the required product name. - let bestMatchedProductName = bestMatch(for: productRef.name, from: Array(allModuleNames)) + // Find a product name from the available dependencies. Depending on how + // the productRef is defined, "available dependencies" might be: + // - modules within the current package + // - products across all packages in the graph + // - products from a given package var packageContainingBestMatchedProduct: String? - if let bestMatchedProductName, productRef.name == bestMatchedProductName { - let dependentPackages = packageBuilder.dependencies.map(\.package) - for p in dependentPackages where p.modules.contains(where: { $0.name == bestMatchedProductName }) { - packageContainingBestMatchedProduct = p.identity.description - break + var bestMatchedProductName: String? + if productRef.package == nil { + // First assume that it's a dependency on modules + // within the same package. + let localModules = Array( + packageBuilder.modules.map(\.module.name) + .filter { $0 != moduleBuilder.module.name } + ) + bestMatchedProductName = bestMatch( + for: productRef.name, + from: localModules + ) + if bestMatchedProductName == nil { + // Search again across all the products, since there's no match + // within the local modules. + bestMatchedProductName = bestMatch( + for: productRef.name, + from: Array(allProductNames) + ) + if bestMatchedProductName != nil { + packageContainingBestMatchedProduct = packageIdForProduct[bestMatchedProductName!] + } + } + } else { + // productRef has a package reference, we shall + // only look for matches within that package. + bestMatchedProductName = bestMatch( + for: productRef.name, + from: Array(productNamesForPackage[productRef.package!] ?? []) + ) + if bestMatchedProductName != nil { + packageContainingBestMatchedProduct = productRef.package } } let error = PackageGraphError.productDependencyNotFound( diff --git a/Tests/PackageGraphTests/ModulesGraphTests.swift b/Tests/PackageGraphTests/ModulesGraphTests.swift index c98d6862d7f..a5e649a0c16 100644 --- a/Tests/PackageGraphTests/ModulesGraphTests.swift +++ b/Tests/PackageGraphTests/ModulesGraphTests.swift @@ -1003,7 +1003,7 @@ final class ModulesGraphTests: XCTestCase { displayName: "weather", path: "/weather", products: [ - ProductDescription(name: "Rain", type: .library(.automatic), targets: ["Rain"]) + ProductDescription(name: "Rain", type: .library(.automatic), targets: ["Rain"]), ], targets: [ TargetDescription(name: "Rain"), @@ -1023,7 +1023,152 @@ final class ModulesGraphTests: XCTestCase { testDiagnostics(observability.diagnostics) { result in result.check( - diagnostic: "product 'Rail' required by package 'forecast' target 'Forecast' not found. Did you mean 'Rain'?", + diagnostic: "product 'Rail' required by package 'forecast' target 'Forecast' not found. Did you mean '.product(name: \"Rain\", package: \"weather\")'?", + severity: .error + ) + } + } + + func testProductDependencyWithSimilarNamesFromMultiplePackages() throws { + let fs = InMemoryFileSystem( + emptyFiles: + "/flavors/Sources/Bitter/Bitter.swift", + "/farm/Sources/Butter/Butter.swift", + "/grocery/Sources/Grocery/Grocery.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + _ = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createFileSystemManifest( + displayName: "flavors", + path: "/flavors", + products: [ProductDescription(name: "Bitter", type: .library(.automatic), targets: ["Bitter"])], + targets: [ + TargetDescription(name: "Bitter"), + ] + ), + Manifest.createFileSystemManifest( + displayName: "farm", + path: "/farm", + products: [ProductDescription(name: "Butter", type: .library(.automatic), targets: ["Butter"])], + targets: [ + TargetDescription(name: "Butter"), + ] + ), + Manifest.createRootManifest( + displayName: "grocery", + path: "/grocery", + dependencies: [.fileSystem(path: "/farm"), .fileSystem(path: "/flavors")], + targets: [ + TargetDescription(name: "Grocery", dependencies: [ + .product(name: "Biter", package: "farm"), + .product(name: "Bitter", package: "flavors"), + ]), + ] + ), + ], + observabilityScope: observability.topScope + ) + + // We should expect matching to work only within the package we want even + // though there are lexically closer candidates in other packages. + testDiagnostics(observability.diagnostics) { result in + result.check( + diagnostic: "product 'Biter' required by package 'grocery' target 'Grocery' not found in package 'farm'. Did you mean '.product(name: \"Butter\", package: \"farm\")'?", + severity: .error + ) + } + } + + func testProductDependencyWithSimilarNamesFromProductTargetsNotProducts() throws { + let fs = InMemoryFileSystem( + emptyFiles: + "/lunch/Sources/Lunch/Lunch.swift", + "/sandwich/Sources/Sandwich/Sandwich.swift", + "/sandwich/Sources/Bread/Bread.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + _ = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createFileSystemManifest( + displayName: "sandwich", + path: "/sandwich", + products: [ProductDescription( + name: "Sandwich", + type: .library(.automatic), + targets: ["Sandwich"] + )], + targets: [ + TargetDescription(name: "Sandwich", dependencies: ["Bread"]), + TargetDescription(name: "Bread"), + ] + ), + Manifest.createRootManifest( + displayName: "lunch", + path: "/lunch", + // Depends on a product which isn't actually declared in sandwich, + // but there's a target with the same name. + dependencies: [.fileSystem(path: "/sandwich")], + targets: [ + TargetDescription(name: "Lunch", dependencies: [.product(name: "Bread", package: "sandwich")]), + ] + ), + ], + observabilityScope: observability.topScope + ) + + testDiagnostics(observability.diagnostics) { result in + result.check( + diagnostic: "product 'Bread' required by package 'lunch' target 'Lunch' not found in package 'sandwich'.", + severity: .error + ) + } + } + + func testProductDependencyWithSimilarNamesFromLocalTargetsNotPackageProducts() throws { + let fs = InMemoryFileSystem( + emptyFiles: + "/gauges/Sources/Chart/Chart.swift", + "/gauges/Sources/Value/Value.swift", + "/controls/Sources/Valve/Valve.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + _ = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createFileSystemManifest( + displayName: "controls", + path: "/controls", + products: [ProductDescription(name: "Valve", type: .library(.automatic), targets: ["Valve"])], + targets: [ + TargetDescription(name: "Valve"), + ] + ), + Manifest.createRootManifest( + displayName: "gauges", + path: "/gauges", + // Target dependency should show the local target dependency, even though + // there's a lexically-close product name in a different package. + dependencies: [.fileSystem(path: "/controls")], + targets: [ + TargetDescription(name: "Chart", dependencies: [ + "Valv", + .product(name: "Valve", package: "controls")]), + TargetDescription(name: "Value"), + ] + ), + ], + observabilityScope: observability.topScope + ) + + testDiagnostics(observability.diagnostics) { result in + result.check( + diagnostic: "product 'Valv' required by package 'gauges' target 'Chart' not found. Did you mean 'Value'?", severity: .error ) } From 17d082949c503814f751f0c7f32a26adad800a77 Mon Sep 17 00:00:00 2001 From: Yury Yurevich Date: Fri, 21 Feb 2025 23:37:15 -0800 Subject: [PATCH 4/5] Extract .productDependencyNotFound error generation into a separate function. --- .../PackageGraph/ModulesGraph+Loading.swift | 178 +++++++++++------- 1 file changed, 109 insertions(+), 69 deletions(-) diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index 58ba3a8ae47..3d36a7ee8b0 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -589,13 +589,6 @@ private func createResolvedPackages( // The set of all module names. var allModuleNames = Set() - // The set of all product names across all packages in the dependency tree. - var allProductNames = Set() - // The set of product names for each package in the dependency tree. - var productNamesForPackage: [String: Set] = [:] - // Reverse lookup: package name or identity for each product. - var packageIdForProduct: [String: String] = [:] - // Track if multiple modules are found with the same name. var foundDuplicateModule = false @@ -637,13 +630,6 @@ private func createResolvedPackages( let manifestProducts = dependency.package.manifest.products.lazy.map { $0.name } let explicitProducts = dependency.package.products.filter { manifestProducts.contains($0.name) } let explicitIdsOrNames = Set(explicitProducts.lazy.map({ lookupByProductIDs ? $0.identity : $0.name })) - let explicitNames = Set(explicitProducts.map { $0.name }) - allProductNames.formUnion(explicitNames) - productNamesForPackage[dependency.package.identity.description] = explicitNames - packageIdForProduct.merge( - explicitNames.map { ($0, dependency.package.identity.description) }, - uniquingKeysWith: { _, new in new } - ) return dependency.products.filter({ lookupByProductIDs ? explicitIdsOrNames.contains($0.product.identity) : explicitIdsOrNames.contains($0.product.name) }) }) @@ -699,61 +685,11 @@ private func createResolvedPackages( // found errors when there are more important errors to // resolve (like authentication issues). if !observabilityScope.errorsReportedInAnyScope { - // Emit error if a product (not module) declared in the package is also a productRef (dependency) - let declProductsAsDependency = package.products.filter { product in - lookupByProductIDs ? product.identity == productRef.identity : product.name == productRef.name - }.map {$0.modules}.flatMap{$0}.filter { t in - t.name != productRef.name - } - - // Find a product name from the available dependencies. Depending on how - // the productRef is defined, "available dependencies" might be: - // - modules within the current package - // - products across all packages in the graph - // - products from a given package - var packageContainingBestMatchedProduct: String? - var bestMatchedProductName: String? - if productRef.package == nil { - // First assume that it's a dependency on modules - // within the same package. - let localModules = Array( - packageBuilder.modules.map(\.module.name) - .filter { $0 != moduleBuilder.module.name } - ) - bestMatchedProductName = bestMatch( - for: productRef.name, - from: localModules - ) - if bestMatchedProductName == nil { - // Search again across all the products, since there's no match - // within the local modules. - bestMatchedProductName = bestMatch( - for: productRef.name, - from: Array(allProductNames) - ) - if bestMatchedProductName != nil { - packageContainingBestMatchedProduct = packageIdForProduct[bestMatchedProductName!] - } - } - } else { - // productRef has a package reference, we shall - // only look for matches within that package. - bestMatchedProductName = bestMatch( - for: productRef.name, - from: Array(productNamesForPackage[productRef.package!] ?? []) - ) - if bestMatchedProductName != nil { - packageContainingBestMatchedProduct = productRef.package - } - } - let error = PackageGraphError.productDependencyNotFound( - package: package.identity.description, - moduleName: moduleBuilder.module.name, - dependencyProductName: productRef.name, - dependencyPackageName: productRef.package, - dependencyProductInDecl: !declProductsAsDependency.isEmpty, - similarProductName: bestMatchedProductName, - packageContainingSimilarProduct: packageContainingBestMatchedProduct + let error = prepareProductDependencyNotFoundError( + packageBuilder: packageBuilder, + moduleBuilder: moduleBuilder, + dependency: productRef, + lookupByProductIDs: lookupByProductIDs ) packageObservabilityScope.emit(error) } @@ -882,6 +818,110 @@ private func createResolvedPackages( return IdentifiableSet(try packageBuilders.map { try $0.construct() }) } +private func prepareProductDependencyNotFoundError( + packageBuilder: ResolvedPackageBuilder, + moduleBuilder: ResolvedModuleBuilder, + dependency: Module.ProductReference, + lookupByProductIDs: Bool +) -> PackageGraphError { + let packageName = packageBuilder.package.identity.description + // Module's dependency is either a local module or a product from another package. + // If dependency is a product from the current package, that's an incorrect + // declaration of the dependency and we should show relevant error. Let's see + // if indeed the dependency matches any of the products. + let declProductsAsDependency = packageBuilder.package.products.filter { product in + lookupByProductIDs ? product.identity == dependency.identity : product.name == dependency.name + }.map(\.modules).flatMap { $0 }.filter { t in + t.name != dependency.name + } + if !declProductsAsDependency.isEmpty { + return PackageGraphError.productDependencyNotFound( + package: packageName, + moduleName: moduleBuilder.module.name, + dependencyProductName: dependency.name, + dependencyPackageName: dependency.package, + dependencyProductInDecl: true, + similarProductName: nil, + packageContainingSimilarProduct: nil + ) + } + + // If dependency name is a typo, find best possible match from the available destinations. + // Depending on how the dependency is declared, "available destinations" might be: + // - modules within the current package + // - products across all packages in the graph + // - products from a specific package + var packageContainingBestMatchedProduct: String? + var bestMatchedProductName: String? + if dependency.package == nil { + // First assume it's a dependency on modules within the same package. + let localModules = Array(packageBuilder.modules.map(\.module.name).filter { $0 != moduleBuilder.module.name }) + bestMatchedProductName = bestMatch(for: dependency.name, from: localModules) + if bestMatchedProductName != nil { + return PackageGraphError.productDependencyNotFound( + package: packageName, + moduleName: moduleBuilder.module.name, + dependencyProductName: dependency.name, + dependencyPackageName: nil, + dependencyProductInDecl: false, + similarProductName: bestMatchedProductName, + packageContainingSimilarProduct: nil + ) + } + // Since there's no package name in the dependency declaration, and no match across + // the local modules, we assume the user actually meant to use product dependency, + // but didn't specify package to use the product from. Since products are globally + // unique, we should be able to find a good match across the graph, if the package + // is already a part of the dependency tree. + let availableProducts = Dictionary( + uniqueKeysWithValues: packageBuilder.dependencies + .flatMap { (packageDep: ResolvedPackageBuilder) -> [( + String, + String + )] in + let manifestProducts = packageDep.package.manifest.products.map(\.name) + let explicitProducts = packageDep.package.products.filter { manifestProducts.contains($0.name) } + let explicitIdsOrNames = Set(explicitProducts.map { lookupByProductIDs ? $0.identity : $0.name }) + return explicitIdsOrNames.map { ($0, packageDep.package.identity.description) } + } + ) + bestMatchedProductName = bestMatch(for: dependency.name, from: Array(availableProducts.keys)) + if bestMatchedProductName != nil { + packageContainingBestMatchedProduct = availableProducts[bestMatchedProductName!] + } + return PackageGraphError.productDependencyNotFound( + package: packageName, + moduleName: moduleBuilder.module.name, + dependencyProductName: dependency.name, + dependencyPackageName: nil, + dependencyProductInDecl: false, + similarProductName: bestMatchedProductName, + packageContainingSimilarProduct: packageContainingBestMatchedProduct + ) + } else { + // Package is explicitly listed in the product dependency, we shall search + // within the products from that package. + let availableProducts = packageBuilder.dependencies + .filter { $0.package.identity.description == dependency.package } + .flatMap { (packageDep: ResolvedPackageBuilder) -> [String] in + let manifestProducts = packageDep.package.manifest.products.map(\.name) + let explicitProducts = packageDep.package.products.filter { manifestProducts.contains($0.name) } + let explicitIdsOrNames = Set(explicitProducts.map { lookupByProductIDs ? $0.identity : $0.name }) + return Array(explicitIdsOrNames) + } + bestMatchedProductName = bestMatch(for: dependency.name, from: availableProducts) + return PackageGraphError.productDependencyNotFound( + package: packageName, + moduleName: moduleBuilder.module.name, + dependencyProductName: dependency.name, + dependencyPackageName: dependency.package, + dependencyProductInDecl: false, + similarProductName: bestMatchedProductName, + packageContainingSimilarProduct: dependency.package + ) + } +} + private func emitDuplicateProductDiagnostic( productName: String, packages: [Package], From 65961c91dd4e1f0e3045b2111300822b9fe7d2ea Mon Sep 17 00:00:00 2001 From: Yury Yurevich Date: Wed, 26 Feb 2025 15:26:03 -0800 Subject: [PATCH 5/5] Condense map+flatMap into single flatMap. --- Sources/PackageGraph/ModulesGraph+Loading.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index 3d36a7ee8b0..20d881b639f 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -831,7 +831,7 @@ private func prepareProductDependencyNotFoundError( // if indeed the dependency matches any of the products. let declProductsAsDependency = packageBuilder.package.products.filter { product in lookupByProductIDs ? product.identity == dependency.identity : product.name == dependency.name - }.map(\.modules).flatMap { $0 }.filter { t in + }.flatMap(\.modules).filter { t in t.name != dependency.name } if !declProductsAsDependency.isEmpty {