Repository: pantsbuild/pants

Issue:
`__defaults__` silently allows-and-ignores fallthrough kwargs
**Describe the bug**
I was trying to do:
```python
__defaults__(python_test=dict(environment="focal-docker"))
```

and getting frustrated it wasn't working.

Turns out it just gets collected and ignored:
https://github.com/pantsbuild/pants/blob/3ce90948d2dc49dd525ca4a10daeaba98068a8e0/src/python/pants/engine/internals/defaults.py#L117

**Pants version**
`2.16.0rc2`

**OS**
N/A

**Additional info**
N/A



Discussion:

Resolving patch:
diff --git a/src/python/pants/engine/internals/defaults.py b/src/python/pants/engine/internals/defaults.py
--- a/src/python/pants/engine/internals/defaults.py
+++ b/src/python/pants/engine/internals/defaults.py
@@ -114,7 +114,7 @@ def set_defaults(
         all: SetDefaultsValueT | None = None,
         extend: bool = False,
         ignore_unknown_fields: bool = False,
-        **kwargs,
+        ignore_unknown_targets: bool = False,
     ) -> None:
         defaults: dict[str, dict[str, Any]] = (
             {} if not extend else {k: dict(v) for k, v in self.defaults.items()}
@@ -125,10 +125,16 @@ def set_defaults(
                 defaults,
                 {tuple(self.registered_target_types.aliases): all},
                 ignore_unknown_fields=True,
+                ignore_unknown_targets=ignore_unknown_targets,
             )
 
         for arg in args:
-            self._process_defaults(defaults, arg, ignore_unknown_fields=ignore_unknown_fields)
+            self._process_defaults(
+                defaults,
+                arg,
+                ignore_unknown_fields=ignore_unknown_fields,
+                ignore_unknown_targets=ignore_unknown_targets,
+            )
 
         # Update with new defaults, dropping targets without any default values.
         for tgt, default in defaults.items():
@@ -148,6 +154,7 @@ def _process_defaults(
         defaults: dict[str, dict[str, Any]],
         targets_defaults: SetDefaultsT,
         ignore_unknown_fields: bool = False,
+        ignore_unknown_targets: bool = False,
     ):
         if not isinstance(targets_defaults, dict):
             raise ValueError(
@@ -168,6 +175,8 @@ def _process_defaults(
             for target_alias in map(str, targets):
                 if target_alias in types:
                     target_type = types[target_alias]
+                elif ignore_unknown_targets:
+                    continue
                 else:
                     raise ValueError(f"Unrecognized target type {target_alias} in {self.address}.")
 
diff --git a/src/python/pants/engine/internals/parser.py b/src/python/pants/engine/internals/parser.py
--- a/src/python/pants/engine/internals/parser.py
+++ b/src/python/pants/engine/internals/parser.py
@@ -199,10 +199,17 @@ def get_env(self, name: str, *args, **kwargs) -> Any:
         """
     )
     def set_defaults(
-        self, *args: SetDefaultsT, ignore_unknown_fields: bool = False, **kwargs
+        self,
+        *args: SetDefaultsT,
+        ignore_unknown_fields: bool = False,
+        ignore_unknown_targets: bool = False,
+        **kwargs,
     ) -> None:
         self.defaults.set_defaults(
-            *args, ignore_unknown_fields=self.is_bootstrap or ignore_unknown_fields, **kwargs
+            *args,
+            ignore_unknown_fields=self.is_bootstrap or ignore_unknown_fields,
+            ignore_unknown_targets=self.is_bootstrap or ignore_unknown_targets,
+            **kwargs,
         )
 
     def set_dependents_rules(self, *args, **kwargs) -> None:
@@ -498,6 +505,9 @@ def __init__(self, name: str) -> None:
         self.args: tuple[Any, ...] = ()
         self.kwargs: dict[str, Any] = {}
 
+    def __hash__(self) -> int:
+        return hash(self.name)
+
     def __call__(self, *args, **kwargs) -> _UnrecognizedSymbol:
         self.args = args
         self.kwargs = kwargs


Test patch:
diff --git a/src/python/pants/engine/internals/parser_test.py b/src/python/pants/engine/internals/parser_test.py
--- a/src/python/pants/engine/internals/parser_test.py
+++ b/src/python/pants/engine/internals/parser_test.py
@@ -171,6 +171,28 @@ class TestField(StringField):
         TestField(raw_field, Address(""))
 
 
+def test_unknown_target_for_defaults_during_bootstrap_issue_19445(
+    defaults_parser_state: BuildFileDefaultsParserState,
+) -> None:
+    parser = Parser(
+        build_root="",
+        registered_target_types=RegisteredTargetTypes({}),
+        union_membership=UnionMembership({}),
+        object_aliases=BuildFileAliases(),
+        ignore_unrecognized_symbols=True,
+    )
+    parser.parse(
+        "BUILD",
+        "__defaults__({'type_1': dict(), type_2: dict()})",
+        BuildFilePreludeSymbols.create({}, ()),
+        EnvironmentVars({}),
+        True,
+        defaults_parser_state,
+        dependents_rules=None,
+        dependencies_rules=None,
+    )
+
+
 @pytest.mark.parametrize("symbol", ["a", "bad", "BAD", "a___b_c", "a231", "áç"])
 def test_extract_symbol_from_name_error(symbol: str) -> None:
     assert _extract_symbol_from_name_error(NameError(f"name '{symbol}' is not defined")) == symbol