Repository: omni-us/jsonargparse

Issue:
Link arguments "Target key must be for an individual argument"
<!-- If you like this project, please ⭐ star it https://github.com/omni-us/jsonargparse/ -->

## 🐛 Bug report

Certain argument links produce an error. For example, using a subclass of the `LightningCLI` that links the trainer `default_root_dir` to the logger `save_dir` arguments, the following error occurs.

```bash
Traceback (most recent call last):
...
  File ".../clitest.py", line 8, in add_arguments_to_parser
    parser.link_arguments("trainer.default_root_dir", "trainer.logger.init_args.save_dir")
  File ".../lib/python3.10/site-packages/jsonargparse/link_arguments.py", line 379, in link_arguments
    ActionLink(self, source, target, compute_fn, apply_on)
  File ".../lib/python3.10/site-packages/jsonargparse/link_arguments.py", line 141, in __init__
    raise ValueError(f'Target key "{target}" must be for an individual argument.')
ValueError: Target key "trainer.logger.init_args.save_dir" must be for an individual argument.
```

A link in the the other direction `save_dir` -> `default_root_dir` seems to work okay,
I suspect the issue may be to do with the fact that the target key exists within an optional `trainer.logger` class.

### To reproduce

```python
from pytorch_lightning.cli import LightningCLI
from pytorch_lightning.demos.boring_classes import BoringDataModule, DemoModel


class MyCLI(LightningCLI):
    def add_arguments_to_parser(self, parser) -> None:
        #parser.link_arguments("trainer.logger.init_args.save_dir", "trainer.default_root_dir") # works 
        parser.link_arguments("trainer.default_root_dir", "trainer.logger.init_args.save_dir") # error


def cli_main():
    cli = MyCLI(DemoModel, BoringDataModule)


if __name__ == "__main__":
    cli_main()
```

### Expected behavior


I'm opening an issue here as:
- It would be nice to allow the link to be ignored when the target is null (perhaps with an additional parameter to the `link_arguments` function e.g. `ignore_null_target`)
- In any case, the error message could be improved.



### Environment

<!-- Fill in the list below. -->
- jsonargparse 4.18.0
- Python 3.10.6

Comments:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 6a9373a..688ae1c 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -43,6 +43,8 @@ Fixed
 - ``fail_untyped=False`` not propagated to subclass ``--*.help`` actions.
 - Issues reported by CodeQL.
 - Incorrect value when ``Path`` is cast to ``str`` and ``rel_path`` was changed.
+- Argument links with target a subclass mixed with other types not working `#208
+  <https://github.com/omni-us/jsonargparse/issues/208>`__.
 
 Changed
 ^^^^^^^
diff --git a/jsonargparse/link_arguments.py b/jsonargparse/link_arguments.py
index 0329aab..97030b3 100644
--- a/jsonargparse/link_arguments.py
+++ b/jsonargparse/link_arguments.py
@@ -135,7 +135,7 @@ class ActionLink(Action):
         assert self.target[1] is not None
 
         from .typehints import ActionTypeHint
-        is_target_subclass = ActionTypeHint.is_subclass_typehint(self.target[1])
+        is_target_subclass = ActionTypeHint.is_subclass_typehint(self.target[1], all_subtypes=False)
         valid_target_init_arg = is_target_subclass and target.startswith(self.target[1].dest+'.init_args.')
         valid_target_leaf = self.target[1].dest == target
         if not valid_target_leaf and is_target_subclass and not valid_target_init_arg:
@@ -321,7 +321,7 @@ class ActionLink(Action):
     def set_target_value(action: 'ActionLink', value: Any, cfg: Namespace, logger) -> None:
         target_key, target_action = action.target
         from .typehints import ActionTypeHint
-        if ActionTypeHint.is_subclass_typehint(target_action):
+        if ActionTypeHint.is_subclass_typehint(target_action, all_subtypes=False):
             if target_key == target_action.dest:  # type: ignore
                 target_action._check_type(value)  # type: ignore
             elif target_key not in cfg:


Test patch:
diff --git a/jsonargparse_tests/test_link_arguments.py b/jsonargparse_tests/test_link_arguments.py
index 1c69b25..eae5f32 100755
--- a/jsonargparse_tests/test_link_arguments.py
+++ b/jsonargparse_tests/test_link_arguments.py
@@ -183,6 +183,30 @@ class LinkArgumentsTests(unittest.TestCase):
                 parser.parse_args(['--a='+json.dumps(a_value), '--c=calendar.Calendar'])
 
 
+    def test_link_arguments_on_parse_mixed_subclass_target(self):
+        class Logger:
+            def __init__(self, save_dir: Optional[str] = None):
+                pass
+
+        class Trainer:
+            def __init__(
+                self,
+                save_dir: Optional[str] = None,
+                logger: Union[bool, Logger] = False,
+            ):
+                pass
+
+        with mock_module(Logger):
+            parser = ArgumentParser()
+            parser.add_class_arguments(Trainer, 'trainer')
+            parser.link_arguments('trainer.save_dir', 'trainer.logger.init_args.save_dir')
+            cfg = parser.parse_args([])
+            self.assertEqual(cfg.trainer, Namespace(logger=False, save_dir=None))
+            cfg = parser.parse_args(['--trainer.save_dir=logs', '--trainer.logger=Logger'])
+            self.assertEqual(cfg.trainer.save_dir, 'logs')
+            self.assertEqual(cfg.trainer.logger.init_args, Namespace(save_dir='logs'))
+
+
     def test_link_arguments_on_parse_add_subclass_arguments_with_instantiate_false(self):
         class ClassA:
             def __init__(