Repository: pantsbuild/pants

Issue:
Parametrize with only one arg broken
**Describe the bug**
You can not parametrize a regular (non-generator) target with a single value (i.e. it is not parametrized at all)

**Pants version**
`main`


**Additional info**

The parametrization correctly deduces that there is no parametrization going on, but fails to unwrap the parametrization object on the field value.

The log lines added below are from:
https://github.com/pantsbuild/pants/blob/f5de7c4e2538376bede255be27ef32afae56ccf0/src/python/pants/engine/internals/graph.py#L306-L309

as: 
```python
    else:
        logger.info(f"ADAPTOR: {target_adaptor}, kwargs: {target_adaptor.kwargs}")
        first, *rest = Parametrize.expand(address, target_adaptor.kwargs)
        logger.info(f"first, rest: {first} : {rest}")
        if rest:
            # The target was parametrized, and so the original Target does not exist.
```

```
10:52:54.40 [INFO] ADAPTOR: TargetAdaptor(type_alias=python_requirement, name=pydevd-pycharm), kwargs: {'resolve': parametrize(python-default), 'requirements': ['pydevd-pycharm==203.5419.8']}
10:52:54.40 [INFO] first, rest: (Address(3rdparty/python:pydevd-pycharm@resolve=python-default), {'requirements': ['pydevd-pycharm==203.5419.8'], 'resolve': 'python-default'}) : []
10:52:54.40 [ERROR] 1 Exception encountered:

Engine traceback:
  in select
  in pants.backend.project_info.peek.peek (default_env)
  in pants.engine.internals.graph.resolve_unexpanded_targets (default_env)
  in pants.engine.internals.specs_rules.resolve_addresses_from_specs (default_env)
  in pants.engine.internals.specs_rules.resolve_addresses_from_raw_specs (default_env)
  in pants.engine.internals.specs_rules.addresses_from_raw_specs_without_file_owners (default_env)
  in pants.engine.internals.graph.resolve_target_parametrizations (3rdparty/python:pydevd-pycharm, default_env)
Traceback (most recent call last):
  File "/Users/andreas.stenius/src/github/kaos/pants/src/python/pants/engine/internals/selectors.py", line 593, in native_engine_generator_send
    res = func.send(arg)
  File "/Users/andreas.stenius/src/github/kaos/pants/src/python/pants/engine/internals/graph.py", line 327, in resolve_target_parametrizations
    target = target_type(
  File "/Users/andreas.stenius/src/github/kaos/pants/src/python/pants/util/meta.py", line 164, in new_init
    prev_init(self, *args, **kwargs)
  File "/Users/andreas.stenius/src/github/kaos/pants/src/python/pants/engine/target.py", line 414, in __init__
    self.field_values = self._calculate_field_values(
  File "/Users/andreas.stenius/src/github/kaos/pants/src/python/pants/engine/target.py", line 449, in _calculate_field_values
    field_values[field_type] = field_type(value, address)
  File "/Users/andreas.stenius/src/github/kaos/pants/src/python/pants/engine/target.py", line 239, in __init__
    super().__init__(raw_value, address)
  File "/Users/andreas.stenius/src/github/kaos/pants/src/python/pants/util/meta.py", line 164, in new_init
    prev_init(self, *args, **kwargs)
  File "/Users/andreas.stenius/src/github/kaos/pants/src/python/pants/engine/target.py", line 139, in __init__
    self.value: Optional[ImmutableValue] = self.compute_value(raw_value, address)
  File "/Users/andreas.stenius/src/github/kaos/pants/src/python/pants/engine/target.py", line 1740, in compute_value
    value_or_default = super().compute_value(raw_value, address)
  File "/Users/andreas.stenius/src/github/kaos/pants/src/python/pants/engine/target.py", line 1639, in compute_value
    raise InvalidFieldTypeException(
pants.engine.target.InvalidFieldTypeException: The 'resolve' field in target 3rdparty/python:pydevd-pycharm must be a string, but was `parametrize(python-default)` with type `Parametrize`.
```



Discussion:

Resolving patch:
diff --git a/src/python/pants/engine/internals/graph.py b/src/python/pants/engine/internals/graph.py
--- a/src/python/pants/engine/internals/graph.py
+++ b/src/python/pants/engine/internals/graph.py
@@ -366,8 +366,9 @@ def _target_parametrizations(
     target_type: type[Target],
     union_membership: UnionMembership,
 ) -> _TargetParametrization:
-    first, *rest = Parametrize.expand(address, target_adaptor.kwargs)
-    if rest:
+    expanded_parametrizations = tuple(Parametrize.expand(address, target_adaptor.kwargs))
+    first_address, first_kwargs = expanded_parametrizations[0]
+    if first_address is not address:
         # The target was parametrized, and so the original Target does not exist.
         generated = FrozenDict(
             (
@@ -380,7 +381,7 @@ def _target_parametrizations(
                     description_of_origin=target_adaptor.description_of_origin,
                 ),
             )
-            for parameterized_address, parameterized_fields in (first, *rest)
+            for parameterized_address, parameterized_fields in expanded_parametrizations
         )
         return _TargetParametrization(None, generated)
     else:


Test patch:
diff --git a/src/python/pants/engine/internals/graph_test.py b/src/python/pants/engine/internals/graph_test.py
--- a/src/python/pants/engine/internals/graph_test.py
+++ b/src/python/pants/engine/internals/graph_test.py
@@ -1464,6 +1464,25 @@ def test_parametrize_16910(generated_targets_rule_runner: RuleRunner, field_cont
         )
 
 
+def test_parametrize_single_value_16978(generated_targets_rule_runner: RuleRunner) -> None:
+    assert_generated(
+        generated_targets_rule_runner,
+        Address("demo"),
+        "generated(resolve=parametrize('demo'), source='f1.ext')",
+        ["f1.ext"],
+        {
+            MockGeneratedTarget(
+                {SingleSourceField.alias: "f1.ext", ResolveField.alias: "demo"},
+                Address("demo", parameters={"resolve": "demo"}),
+                residence_dir="demo",
+            ),
+        },
+        expected_dependencies={
+            "demo@resolve=demo": set(),
+        },
+    )
+
+
 # -----------------------------------------------------------------------------------------------
 # Test `SourcesField`. Also see `engine/target_test.py`.
 # -----------------------------------------------------------------------------------------------