Repository: swiftlang/swift-package-manager
Pull request #8612: Add more context for validation errors for target properties.
Author: login: yyvch
Created: 2025-05-02T19:16:15Z | Merged: 2025-05-09T14:13:32Z

Add more context for validation errors for target properties.

### Motivation:

I came across a post https://forums.swift.org/t/getting-error-on-package-resolve/75419 which reports the error message to be not clear. I thought I could clarify the message.

```
error: 'xerr': target 'Penny' contains a value for disallowed property 'settings'
```

### Modifications:

I've updated the message for the error, so it includes more details to narrow down what is the culprit:
 - highlight that the property depends on the target type
 - show the value of the property, so that there's a hint what line in the Package.swift lead to the error (for example, linkerSettings or cxxSettings)

### Result:


Before:

```
error: 'xerr': target 'Penny' contains a value for disallowed property 'settings'
```

After:

```
error: 'xerr': target 'Penny' is assigned a property 'settings' which is not accepted for
the binary target type. The current value of the property has the following representation: 
[PackageModel.TargetBuildSettingDescription.Setting(
tool: PackageModel.TargetBuildSettingDescription.Tool.linker,
kind: PackageModel.TargetBuildSettingDescription.Kind.linkedFramework("AVFoundation"),
condition: nil)].
```


Diff:
diff --git a/Sources/PackageModel/Manifest/TargetDescription.swift b/Sources/PackageModel/Manifest/TargetDescription.swift
index e546e44583a..d336bedb60a 100644
--- a/Sources/PackageModel/Manifest/TargetDescription.swift
+++ b/Sources/PackageModel/Manifest/TargetDescription.swift
@@ -212,51 +212,237 @@ public struct TargetDescription: Hashable, Encodable, Sendable {
         checksum: String? = nil,
         pluginUsages: [PluginUsage]? = nil
     ) throws {
+        let targetType = String(describing: type)
         switch type {
         case .regular, .executable, .test:
-            if url != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "url") }
-            if pkgConfig != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pkgConfig") }
-            if providers != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "providers") }
-            if pluginCapability != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginCapability") }
-            if checksum != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "checksum") }
+            if url != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "url",
+                value: url ?? "<nil>"
+            ) }
+            if pkgConfig != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "pkgConfig",
+                value: pkgConfig ?? "<nil>"
+            ) }
+            if providers != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "providers",
+                value: String(describing: providers!)
+            ) }
+            if pluginCapability != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "pluginCapability",
+                value: String(describing: pluginCapability!)
+            ) }
+            if checksum != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "checksum",
+                value: checksum ?? "<nil>"
+            ) }
         case .system:
-            if !dependencies.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "dependencies") }
-            if !exclude.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "exclude") }
-            if sources != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "sources") }
-            if !resources.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "resources") }
-            if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "publicHeadersPath") }
-            if pluginCapability != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginCapability") }
-            if !settings.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "settings") }
-            if checksum != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "checksum") }
-            if pluginUsages != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginUsages") }
+            if !dependencies.isEmpty { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "dependencies",
+                value: String(describing: dependencies)
+            ) }
+            if !exclude.isEmpty { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "exclude",
+                value: String(describing: exclude)
+            ) }
+            if sources != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "sources",
+                value: String(describing: sources!)
+            ) }
+            if !resources.isEmpty { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "resources",
+                value: String(describing: resources)
+            ) }
+            if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "publicHeadersPath",
+                value: publicHeadersPath ?? "<nil>"
+            ) }
+            if pluginCapability != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "pluginCapability",
+                value: String(describing: pluginCapability!)
+            ) }
+            if !settings.isEmpty { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "settings",
+                value: String(describing: settings)
+            ) }
+            if checksum != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "checksum",
+                value: checksum ?? "<nil>"
+            ) }
+            if pluginUsages != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "pluginUsages",
+                value: String(describing: pluginUsages!)
+            ) }
         case .binary:
             if path == nil && url == nil { throw Error.binaryTargetRequiresEitherPathOrURL(targetName: name) }
-            if !dependencies.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "dependencies") }
-            if !exclude.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "exclude") }
-            if sources != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "sources") }
-            if !resources.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "resources") }
-            if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "publicHeadersPath") }
-            if pkgConfig != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pkgConfig") }
-            if providers != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "providers") }
-            if pluginCapability != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginCapability") }
-            if !settings.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "settings") }
-            if pluginUsages != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginUsages") }
+            if !dependencies.isEmpty { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "dependencies",
+                value: String(describing: dependencies)
+            ) }
+            if !exclude.isEmpty { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "exclude",
+                value: String(describing: exclude)
+            ) }
+            if sources != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "sources",
+                value: String(describing: sources!)
+            ) }
+            if !resources.isEmpty { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "resources",
+                value: String(describing: resources)
+            ) }
+            if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "publicHeadersPath",
+                value: publicHeadersPath ?? "<nil>"
+            ) }
+            if pkgConfig != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "pkgConfig",
+                value: pkgConfig ?? "<nil>"
+            ) }
+            if providers != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "providers",
+                value: String(describing: providers!)
+            ) }
+            if pluginCapability != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "pluginCapability",
+                value: String(describing: pluginCapability!)
+            ) }
+            if !settings.isEmpty { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "settings",
+                value: String(describing: settings)
+            ) }
+            if pluginUsages != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "pluginUsages",
+                value: String(describing: pluginUsages!)
+            ) }
         case .plugin:
-            if url != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "url") }
-            if !resources.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "resources") }
-            if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "publicHeadersPath") }
-            if pkgConfig != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pkgConfig") }
-            if providers != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "providers") }
-            if pluginCapability == nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginCapability") }
-            if !settings.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "settings") }
-            if pluginUsages != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginUsages") }
+            if pluginCapability == nil { throw Error.pluginTargetRequiresPluginCapability(targetName: name) }
+            if url != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "url",
+                value: url ?? "<nil>"
+            ) }
+            if !resources.isEmpty { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "resources",
+                value: String(describing: resources)
+            ) }
+            if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "publicHeadersPath",
+                value: publicHeadersPath ?? "<nil>"
+            ) }
+            if pkgConfig != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "pkgConfig",
+                value: pkgConfig ?? "<nil>"
+            ) }
+            if providers != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "providers",
+                value: String(describing: providers!)
+            ) }
+            if !settings.isEmpty { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "settings",
+                value: String(describing: settings)
+            ) }
+            if pluginUsages != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "pluginUsages",
+                value: String(describing: pluginUsages!)
+            ) }
         case .macro:
-            if url != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "url") }
-            if !resources.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "resources") }
-            if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "publicHeadersPath") }
-            if pkgConfig != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pkgConfig") }
-            if providers != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "providers") }
-            if pluginCapability != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginCapability") }
+            if url != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "url",
+                value: url ?? "<nil>"
+            ) }
+            if !resources.isEmpty { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "resources",
+                value: String(describing: resources)
+            ) }
+            if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "publicHeadersPath",
+                value: publicHeadersPath ?? "<nil>"
+            ) }
+            if pkgConfig != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "pkgConfig",
+                value: pkgConfig ?? "<nil>"
+            ) }
+            if providers != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "providers",
+                value: String(describing: providers!)
+            ) }
+            if pluginCapability != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "pluginCapability",
+                value: String(describing: pluginCapability!)
+            ) }
         }
 
         self.name = name
@@ -404,14 +590,19 @@ import protocol Foundation.LocalizedError
 
 private enum Error: LocalizedError, Equatable {
     case binaryTargetRequiresEitherPathOrURL(targetName: String)
-    case disallowedPropertyInTarget(targetName: String, propertyName: String)
-    
+    case pluginTargetRequiresPluginCapability(targetName: String)
+    case disallowedPropertyInTarget(targetName: String, targetType: String, propertyName: String, value: String)
+
     var errorDescription: String? {
         switch self {
         case .binaryTargetRequiresEitherPathOrURL(let targetName):
-            return "binary target '\(targetName)' neither defines neither path nor URL for its artifacts"
-        case .disallowedPropertyInTarget(let targetName, let propertyName):
-            return "target '\(targetName)' contains a value for disallowed property '\(propertyName)'"
+            "binary target '\(targetName)' must define either path or URL for its artifacts"
+        case .pluginTargetRequiresPluginCapability(let targetName):
+            "plugin target '\(targetName)' must define a plugin capability"
+        case .disallowedPropertyInTarget(let targetName, let targetType, let propertyName, let value):
+            "target '\(targetName)' is assigned a property '\(propertyName)' which is not accepted " +
+            "for the \(targetType) target type. The current property value has " +
+            "the following representation: \(value)."
         }
     }
 }
diff --git a/Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift
index 27bbab812d4..0c6e35a4d7f 100644
--- a/Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift
+++ b/Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift
@@ -158,7 +158,38 @@ final class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests {
 
         let observability = ObservabilitySystem.makeForTesting()
         await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in
-            XCTAssertEqual(error.localizedDescription, "target 'Foo' contains a value for disallowed property 'settings'")
+            XCTAssertEqual(error.localizedDescription,
+                "target 'Foo' is assigned a property 'settings' which is not accepted for the binary target type. " +
+                "The current property value has the following representation: " +
+                "[PackageModel.TargetBuildSettingDescription.Setting(" +
+                "tool: PackageModel.TargetBuildSettingDescription.Tool.linker, " +
+                "kind: PackageModel.TargetBuildSettingDescription.Kind.linkedFramework(\"AVFoundation\"), " +
+                "condition: nil)].")
+        }
+    }
+
+    func testBinaryTargetRequiresPathOrUrl() async throws {
+        let content = """
+        import PackageDescription
+        var fwBinaryTarget = Target.binaryTarget(
+            name: "nickel",
+            url: "https://example.com/foo.git",
+            checksum: "bee"
+        )
+        fwBinaryTarget.url = nil
+        let package = Package(name: "foo", targets: [fwBinaryTarget])
+        """
+
+        let observability = ObservabilitySystem.makeForTesting()
+        await XCTAssertAsyncThrowsError(
+            try await loadAndValidateManifest(
+                content, observabilityScope: observability.topScope
+            ), "expected error"
+        ) { error in
+            XCTAssertEqual(
+                error.localizedDescription,
+                "binary target 'nickel' must define either path or URL for its artifacts"
+            )
         }
     }
 
diff --git a/Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift
index ff7d26a11d0..32b31e47301 100644
--- a/Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift
+++ b/Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift
@@ -98,6 +98,30 @@ final class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests {
         XCTAssertEqual(manifest.targets[0].pluginCapability, .buildTool)
     }
 
+    func testPluginTargetRequiresPluginCapability() async throws {
+        let content = """
+        import PackageDescription
+        var fwPluginTarget = Target.plugin(
+            name: "quarter",
+            capability: .buildTool
+        )
+        fwPluginTarget.pluginCapability = nil
+        let package = Package(name: "foo", targets: [fwPluginTarget])
+        """
+
+        let observability = ObservabilitySystem.makeForTesting()
+        await XCTAssertAsyncThrowsError(
+            try await loadAndValidateManifest(
+                content, observabilityScope: observability.topScope
+            ), "expected error"
+        ) { error in
+            XCTAssertEqual(
+                error.localizedDescription,
+                "plugin target 'quarter' must define a plugin capability"
+            )
+        }
+    }
+
     func testPluginTargetCustomization() async throws {
         let content = """
             import PackageDescription


Reviews:
user: login: bkhouri
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T14:03:49Z
user: login: bkhouri
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T14:12:02Z
user: login: bkhouri
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T14:14:14Z
user: login: bkhouri
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T14:15:37Z
user: login: bkhouri
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T14:18:01Z
user: login: yyvch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T18:10:51Z
user: login: yyvch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T18:11:54Z
user: login: yyvch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T18:11:58Z
user: login: yyvch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T18:47:26Z
user: login: bkhouri
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T19:38:41Z
user: login: yyvch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T23:57:18Z
user: login: yyvch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T23:57:30Z
user: login: yyvch
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T06:31:42Z
user: login: bkhouri
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T11:59:24Z
user: login: bripeticca
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T14:02:38Z

Review comments:
diff_hunk: @@ -212,51 +212,237 @@ public struct TargetDescription: Hashable, Encodable, Sendable {
         checksum: String? = nil,
         pluginUsages: [PluginUsage]? = nil
     ) throws {
+        let targetType = String(describing: type)
         switch type {
         case .regular, .executable, .test:
-            if url != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "url") }
-            if pkgConfig != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pkgConfig") }
-            if providers != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "providers") }
-            if pluginCapability != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginCapability") }
-            if checksum != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "checksum") }
+            if url != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "url",
+                value: url ?? "<nil>"
path: Sources/PackageModel/Manifest/TargetDescription.swift
user: login: bkhouri
body: question: Based on the condition, `url != nil`, so when will this code path use `"<nil>"`?

This comment applies to all other similar occurrences.
created_at: 2025-05-07T14:03:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -404,14 +590,19 @@ import protocol Foundation.LocalizedError
 
 private enum Error: LocalizedError, Equatable {
     case binaryTargetRequiresEitherPathOrURL(targetName: String)
-    case disallowedPropertyInTarget(targetName: String, propertyName: String)
-    
+    case pluginTargetRequiresPluginCapability(targetName: String)
path: Sources/PackageModel/Manifest/TargetDescription.swift
user: login: bkhouri
body: thought (possibly-blocking): It's great to see we added an additional error.   However, would it be possible to add a test case to validate this path (if one does no already exist)?
created_at: 2025-05-07T14:12:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -404,14 +590,19 @@ import protocol Foundation.LocalizedError
 
 private enum Error: LocalizedError, Equatable {
     case binaryTargetRequiresEitherPathOrURL(targetName: String)
-    case disallowedPropertyInTarget(targetName: String, propertyName: String)
-    
+    case pluginTargetRequiresPluginCapability(targetName: String)
+    case disallowedPropertyInTarget(targetName: String, targetType: String, propertyName: String, value: String)
+
     var errorDescription: String? {
         switch self {
         case .binaryTargetRequiresEitherPathOrURL(let targetName):
-            return "binary target '\(targetName)' neither defines neither path nor URL for its artifacts"
-        case .disallowedPropertyInTarget(let targetName, let propertyName):
-            return "target '\(targetName)' contains a value for disallowed property '\(propertyName)'"
+            "binary target '\(targetName)' neither defines neither path nor URL for its artifacts"
path: Sources/PackageModel/Manifest/TargetDescription.swift
user: login: bkhouri
body: suggestion: this is not related to your change, but would it be possible to clarify this error message?  maybe something like `"binary target '\(targetName)' must define either path or URL for it's artifacts"`
created_at: 2025-05-07T14:14:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -404,14 +590,19 @@ import protocol Foundation.LocalizedError
 
 private enum Error: LocalizedError, Equatable {
     case binaryTargetRequiresEitherPathOrURL(targetName: String)
-    case disallowedPropertyInTarget(targetName: String, propertyName: String)
-    
+    case pluginTargetRequiresPluginCapability(targetName: String)
+    case disallowedPropertyInTarget(targetName: String, targetType: String, propertyName: String, value: String)
+
     var errorDescription: String? {
         switch self {
         case .binaryTargetRequiresEitherPathOrURL(let targetName):
-            return "binary target '\(targetName)' neither defines neither path nor URL for its artifacts"
-        case .disallowedPropertyInTarget(let targetName, let propertyName):
-            return "target '\(targetName)' contains a value for disallowed property '\(propertyName)'"
+            "binary target '\(targetName)' neither defines neither path nor URL for its artifacts"
+        case .pluginTargetRequiresPluginCapability(let targetName):
+            "plugin target '\(targetName)' does not define any plugin capability"
+        case .disallowedPropertyInTarget(let targetName, let targetType, let propertyName, let value):
+            "target '\(targetName)' is assigned a property '\(propertyName)' which is not accepted " +
+            "for the \(targetType) target type. The current value of the property has " +
path: Sources/PackageModel/Manifest/TargetDescription.swift
user: login: bkhouri
body: suggestion: `s/The current value of the property has/The current property value has`
created_at: 2025-05-07T14:15:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -158,7 +158,13 @@ final class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests {
 
         let observability = ObservabilitySystem.makeForTesting()
         await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in
-            XCTAssertEqual(error.localizedDescription, "target 'Foo' contains a value for disallowed property 'settings'")
+            XCTAssertEqual(error.localizedDescription,
+                "target 'Foo' is assigned a property 'settings' which is not accepted for the binary target type. " +
+                "The current value of the property has the following representation: " +
+                "[PackageModel.TargetBuildSettingDescription.Setting(" +
path: Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift
user: login: bkhouri
body: Question: how does this error message look if there are other valid `settings` value?  Will the error message display both supported and unsupported values, or will it specifically display the unsupported value?
created_at: 2025-05-07T14:18:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -212,51 +212,237 @@ public struct TargetDescription: Hashable, Encodable, Sendable {
         checksum: String? = nil,
         pluginUsages: [PluginUsage]? = nil
     ) throws {
+        let targetType = String(describing: type)
         switch type {
         case .regular, .executable, .test:
-            if url != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "url") }
-            if pkgConfig != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pkgConfig") }
-            if providers != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "providers") }
-            if pluginCapability != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginCapability") }
-            if checksum != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "checksum") }
+            if url != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "url",
+                value: url ?? "<nil>"
path: Sources/PackageModel/Manifest/TargetDescription.swift
user: login: yyvch
body: url is optional string `String?` and value is a string, and I needed to unwrap optional string into string. I could've used `url!`, which I used for example for `pluginCapability`. I thought `url ?? "<nil>"` looks nicer for strings, and I used for strings, and used force unwrap `pluginCapability!` for non-string types.

To answer your question, `<nil>` shouldn't be in any code paths, that's purely to unwrap optional string into string. If you prefer `url!` instead, please let me know.
created_at: 2025-05-07T18:10:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -404,14 +590,19 @@ import protocol Foundation.LocalizedError
 
 private enum Error: LocalizedError, Equatable {
     case binaryTargetRequiresEitherPathOrURL(targetName: String)
-    case disallowedPropertyInTarget(targetName: String, propertyName: String)
-    
+    case pluginTargetRequiresPluginCapability(targetName: String)
+    case disallowedPropertyInTarget(targetName: String, targetType: String, propertyName: String, value: String)
+
     var errorDescription: String? {
         switch self {
         case .binaryTargetRequiresEitherPathOrURL(let targetName):
-            return "binary target '\(targetName)' neither defines neither path nor URL for its artifacts"
-        case .disallowedPropertyInTarget(let targetName, let propertyName):
-            return "target '\(targetName)' contains a value for disallowed property '\(propertyName)'"
+            "binary target '\(targetName)' neither defines neither path nor URL for its artifacts"
path: Sources/PackageModel/Manifest/TargetDescription.swift
user: login: yyvch
body: Thank you for the suggestion, I agree, I will do it.
created_at: 2025-05-07T18:11:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -404,14 +590,19 @@ import protocol Foundation.LocalizedError
 
 private enum Error: LocalizedError, Equatable {
     case binaryTargetRequiresEitherPathOrURL(targetName: String)
-    case disallowedPropertyInTarget(targetName: String, propertyName: String)
-    
+    case pluginTargetRequiresPluginCapability(targetName: String)
path: Sources/PackageModel/Manifest/TargetDescription.swift
user: login: yyvch
body: Ack. I will add it.
created_at: 2025-05-07T18:11:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -158,7 +158,13 @@ final class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests {
 
         let observability = ObservabilitySystem.makeForTesting()
         await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in
-            XCTAssertEqual(error.localizedDescription, "target 'Foo' contains a value for disallowed property 'settings'")
+            XCTAssertEqual(error.localizedDescription,
+                "target 'Foo' is assigned a property 'settings' which is not accepted for the binary target type. " +
+                "The current value of the property has the following representation: " +
+                "[PackageModel.TargetBuildSettingDescription.Setting(" +
path: Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift
user: login: yyvch
body: There's no valid value for this property. The binary target implies that there's no Swift compiler, or C compiler, or linker involved, so any of the settings that are passed to Swift/C/linker for a binary target, considered a logic error.
created_at: 2025-05-07T18:47:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -212,51 +212,237 @@ public struct TargetDescription: Hashable, Encodable, Sendable {
         checksum: String? = nil,
         pluginUsages: [PluginUsage]? = nil
     ) throws {
+        let targetType = String(describing: type)
         switch type {
         case .regular, .executable, .test:
-            if url != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "url") }
-            if pkgConfig != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pkgConfig") }
-            if providers != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "providers") }
-            if pluginCapability != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginCapability") }
-            if checksum != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "checksum") }
+            if url != nil { throw Error.disallowedPropertyInTarget(
+                targetName: name,
+                targetType: targetType,
+                propertyName: "url",
+                value: url ?? "<nil>"
path: Sources/PackageModel/Manifest/TargetDescription.swift
user: login: bkhouri
body: This is fine.
created_at: 2025-05-07T19:38:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -404,14 +590,19 @@ import protocol Foundation.LocalizedError
 
 private enum Error: LocalizedError, Equatable {
     case binaryTargetRequiresEitherPathOrURL(targetName: String)
-    case disallowedPropertyInTarget(targetName: String, propertyName: String)
-    
+    case pluginTargetRequiresPluginCapability(targetName: String)
+    case disallowedPropertyInTarget(targetName: String, targetType: String, propertyName: String, value: String)
+
     var errorDescription: String? {
         switch self {
         case .binaryTargetRequiresEitherPathOrURL(let targetName):
-            return "binary target '\(targetName)' neither defines neither path nor URL for its artifacts"
-        case .disallowedPropertyInTarget(let targetName, let propertyName):
-            return "target '\(targetName)' contains a value for disallowed property '\(propertyName)'"
+            "binary target '\(targetName)' neither defines neither path nor URL for its artifacts"
+        case .pluginTargetRequiresPluginCapability(let targetName):
+            "plugin target '\(targetName)' does not define any plugin capability"
+        case .disallowedPropertyInTarget(let targetName, let targetType, let propertyName, let value):
+            "target '\(targetName)' is assigned a property '\(propertyName)' which is not accepted " +
+            "for the \(targetType) target type. The current value of the property has " +
path: Sources/PackageModel/Manifest/TargetDescription.swift
user: login: yyvch
body: Done in 24d87fb.
created_at: 2025-05-07T23:57:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -404,14 +590,19 @@ import protocol Foundation.LocalizedError
 
 private enum Error: LocalizedError, Equatable {
     case binaryTargetRequiresEitherPathOrURL(targetName: String)
-    case disallowedPropertyInTarget(targetName: String, propertyName: String)
-    
+    case pluginTargetRequiresPluginCapability(targetName: String)
+    case disallowedPropertyInTarget(targetName: String, targetType: String, propertyName: String, value: String)
+
     var errorDescription: String? {
         switch self {
         case .binaryTargetRequiresEitherPathOrURL(let targetName):
-            return "binary target '\(targetName)' neither defines neither path nor URL for its artifacts"
-        case .disallowedPropertyInTarget(let targetName, let propertyName):
-            return "target '\(targetName)' contains a value for disallowed property '\(propertyName)'"
+            "binary target '\(targetName)' neither defines neither path nor URL for its artifacts"
path: Sources/PackageModel/Manifest/TargetDescription.swift
user: login: yyvch
body: Done in 24d87fb.
created_at: 2025-05-07T23:57:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -404,14 +590,19 @@ import protocol Foundation.LocalizedError
 
 private enum Error: LocalizedError, Equatable {
     case binaryTargetRequiresEitherPathOrURL(targetName: String)
-    case disallowedPropertyInTarget(targetName: String, propertyName: String)
-    
+    case pluginTargetRequiresPluginCapability(targetName: String)
path: Sources/PackageModel/Manifest/TargetDescription.swift
user: login: yyvch
body: Done in 3816496.
created_at: 2025-05-08T06:31:41Z
author_association: CONTRIBUTOR

PR comments:
user: login: yyvch
created_at: 2025-05-02T19:18:25Z
body: @swift-ci please test
author_association: CONTRIBUTOR
user: login: yyvch
created_at: 2025-05-08T06:33:20Z
body: @swift-ci please test
author_association: CONTRIBUTOR
user: login: bkhouri
created_at: 2025-05-08T11:57:54Z
body: @swift-ci test windows
author_association: CONTRIBUTOR