Repository: omni-us/jsonargparse

Issue:
Question: How to link arguments where target is a list member
<!-- If you like this project, please ⭐ star it https://github.com/omni-us/jsonargparse/ -->

I'm using PyTorch Lightning and LightningCLI. I have a .yaml specifying a list of trainer callbacks:
```
# trainer.yaml

callbacks:
- class_path: EarlyStopping
  init_args:
    monitor: validation/auroc
    mode: "max"
- class_path: ModelCheckpoint
  init_args:
    monitor: validation/auroclink_arguments
    mode: "max"
    save_top_k: 2
    save_last: true
    dirpath: <should be linked argument>
```

In my use case, the `dirpath` argument to `ModelCheckpoint` needs to be generated from some source variable, resulting in a URI of the form `s3://bucket/folder/run_id/`

However, the `dirpath` argument belongs to `ModelCheckpoint` which is an element of a list. How can I link to this variable using the `jsonargparse.ArgumentLinking.link_arguments` function?

Thanks

Discussion:
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=399)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=CODE_SMELL) [1 Code Smell](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=399) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=399&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=399&metric=new_duplicated_lines_density&view=list)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) Report
All modified lines are covered by tests :white_check_mark:
> Comparison is base [(`dd0f9e3`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/dd0f9e3767fcf2168c183bb346db9d7324bf7c28?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) 100.00% compared to head [(`e6d49ee`)](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) 100.00%.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##              main      #399   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           21        21           
  Lines         5819      5830   +11     
=========================================
+ Hits          5819      5830   +11     
```

| [Flag](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | Coverage Δ | |
|---|---|---|
| [py3.10](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `88.06% <100.00%> (+0.02%)` | :arrow_up: |
| [py3.10_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.69% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.10_pydantic1](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `48.37% <10.00%> (-0.10%)` | :arrow_down: |
| [py3.10_pydantic2](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `48.09% <10.00%> (-0.10%)` | :arrow_down: |
| [py3.10_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.69% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.11](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `88.04% <100.00%> (+0.02%)` | :arrow_up: |
| [py3.11_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.69% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.11_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `98.69% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.7](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `88.64% <100.00%> (+0.02%)` | :arrow_up: |
| [py3.7_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.48% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.7_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.46% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.8](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `88.79% <100.00%> (+0.02%)` | :arrow_up: |
| [py3.8_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.50% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.8_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.48% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.9](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `88.64% <100.00%> (+0.02%)` | :arrow_up: |
| [py3.9_all](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.36% <100.00%> (+<0.01%)` | :arrow_up: |
| [py3.9_types](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | `99.36% <100.00%> (+<0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Files](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | Coverage Δ | |
|---|---|---|
| [jsonargparse/\_link\_arguments.py](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL19saW5rX2FyZ3VtZW50cy5weQ==) | `100.00% <100.00%> (ø)` | |
| [jsonargparse/\_typehints.py](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL190eXBlaGludHMucHk=) | `100.00% <100.00%> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/omni-us/jsonargparse/pull/399?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=399)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=CODE_SMELL) [1 Code Smell](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=399&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=399) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=399&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=399&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 79e1c54..ee689ed 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -12,9 +12,15 @@ The semantic versioning only considers the public API as described in
 paths are considered internals and can change in minor and patch releases.
 
 
-v4.25.1 (2023-10-??)
+v4.26.0 (2023-10-??)
 --------------------
 
+Added
+^^^^^
+- Support for on parse argument links with target subclasses in a list (`#394
+  <https://github.com/omni-us/jsonargparse/issues/394>`__, `lightning#18161
+  <https://github.com/Lightning-AI/lightning/issues/18161>`__).
+
 Fixed
 ^^^^^
 - Failures with subcommands and default_config_files when keys are repeated
diff --git a/DOCUMENTATION.rst b/DOCUMENTATION.rst
index 20bc653..f634fd5 100644
--- a/DOCUMENTATION.rst
+++ b/DOCUMENTATION.rst
@@ -2071,9 +2071,12 @@ There are two types of links, defined with ``apply_on='parse'`` or
 calling one of the parse methods and the latter are set when calling
 :py:meth:`.ArgumentParser.instantiate_classes`.
 
+Applied on parse
+----------------
+
 For parsing links, source keys can be individual arguments or nested groups. The
-target key has to be a single argument. The keys can be inside init_args of a
-subclass. The compute function should accept as many positional arguments as
+target key has to be a single argument. The keys can be inside ``init_args`` of
+a subclass. The compute function should accept as many positional arguments as
 there are sources and return a value of type compatible with the target. An
 example would be the following:
 
@@ -2097,6 +2100,33 @@ example would be the following:
 As argument and in config files only ``data.batch_size`` should be specified.
 Then whatever value it has will be propagated to ``model.batch_size``.
 
+An example of a target being in a subclass is:
+
+.. testcode::
+
+    class Logger:
+        def __init__(self, save_dir: Optional[str] = None):
+            self.save_dir = save_dir
+
+    class Trainer:
+        def __init__(
+            self,
+            save_dir: Optional[str] = None,
+            logger: Union[bool, Logger, List[Logger]] = False,
+        ):
+            self.logger = logger
+
+    parser = ArgumentParser()
+    parser.add_class_arguments(Trainer, "trainer")
+    parser.link_arguments("trainer.save_dir", "trainer.logger.init_args.save_dir")
+
+The link gets applied to the ``logger`` parameter when it is a single subclass
+and applied to all elements of a list of subclasses. If a subclass does not
+define the targeted ``init_args`` parameter, the link is ignored.
+
+Applied on instantiate
+----------------------
+
 For instantiation links, sources can be class groups (added with
 :py:meth:`.SignatureArguments.add_class_arguments`) or subclass arguments (see
 :ref:`sub-classes`). The source key can be the entire instantiated object or an
diff --git a/jsonargparse/_link_arguments.py b/jsonargparse/_link_arguments.py
index 8ccabca..f5f0af2 100644
--- a/jsonargparse/_link_arguments.py
+++ b/jsonargparse/_link_arguments.py
@@ -146,7 +146,7 @@ class ActionLink(Action):
 
         from ._typehints import ActionTypeHint
 
-        is_target_subclass = ActionTypeHint.is_subclass_typehint(self.target[1], all_subtypes=False)
+        is_target_subclass = ActionTypeHint.is_subclass_typehint(self.target[1], all_subtypes=False, also_lists=True)
         valid_target_init_arg = is_target_subclass and target.startswith(f"{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:
@@ -163,7 +163,10 @@ class ActionLink(Action):
                     group._group_actions.remove(self.target[1])
                     if is_target_subclass:
                         help_dest = f"{self.target[1].dest}.help"
-                        group._group_actions.remove(next(a for a in group._group_actions if a.dest == help_dest))
+                        for action in group._group_actions:
+                            if action.dest == help_dest:  # type: ignore
+                                group._group_actions.remove(action)
+                                break
                     if group._group_actions and all(isinstance(a, _ActionConfigLoad) for a in group._group_actions):
                         group.description = (
                             f"Group '{group._group_actions[0].dest}': All arguments are derived from links."
@@ -354,14 +357,23 @@ class ActionLink(Action):
     @staticmethod
     def set_target_value(action: "ActionLink", value: Any, cfg: Namespace, logger) -> None:
         target_key, target_action = action.target
+        assert target_action
         from ._typehints import ActionTypeHint
 
-        if ActionTypeHint.is_subclass_typehint(target_action, all_subtypes=False):
-            if target_key == target_action.dest:  # type: ignore
+        if ActionTypeHint.is_subclass_typehint(target_action, all_subtypes=False, also_lists=True):
+            if target_key == target_action.dest:
                 target_action._check_type(value)  # type: ignore
-            elif target_key not in cfg:
-                logger.debug(f"Link '{action.option_strings[0]}' ignored since target not found in namespace.")
-                return
+            else:
+                parent = cfg.get(target_action.dest)
+                child_key = target_key[len(target_action.dest) + 1 :]
+                if isinstance(parent, list) and any(isinstance(i, Namespace) and child_key in i for i in parent):
+                    for item in parent:
+                        if child_key in item:
+                            item[child_key] = value
+                    return
+                if target_key not in cfg:
+                    logger.debug(f"Link '{action.option_strings[0]}' ignored since target not found.")
+                    return
         cfg[target_key] = value
 
     @staticmethod
diff --git a/jsonargparse/_typehints.py b/jsonargparse/_typehints.py
index b543691..f318ddb 100644
--- a/jsonargparse/_typehints.py
+++ b/jsonargparse/_typehints.py
@@ -254,12 +254,12 @@ class ActionTypeHint(Action):
         return supported
 
     @staticmethod
-    def is_subclass_typehint(typehint, all_subtypes=True):
+    def is_subclass_typehint(typehint, all_subtypes=True, also_lists=False):
         typehint = typehint_from_action(typehint)
         if typehint is None:
             return False
         typehint_origin = get_typehint_origin(typehint)
-        if typehint_origin == Union:
+        if typehint_origin == Union or (also_lists and typehint_origin in sequence_origin_types):
             subtypes = [a for a in typehint.__args__ if a != NoneType]
             test = all if all_subtypes else any
             return test(ActionTypeHint.is_subclass_typehint(s) for s in subtypes)


Test patch:
diff --git a/jsonargparse_tests/test_link_arguments.py b/jsonargparse_tests/test_link_arguments.py
index 538c866..982a1f1 100644
--- a/jsonargparse_tests/test_link_arguments.py
+++ b/jsonargparse_tests/test_link_arguments.py
@@ -217,7 +217,7 @@ class Logger:
         pass
 
 
-class Trainer:
+class TrainerLoggerUnion:
     def __init__(
         self,
         save_dir: Optional[str] = None,
@@ -227,7 +227,7 @@ class Trainer:
 
 
 def test_on_parse_subclass_target_in_union(parser):
-    parser.add_class_arguments(Trainer, "trainer")
+    parser.add_class_arguments(TrainerLoggerUnion, "trainer")
     parser.link_arguments("trainer.save_dir", "trainer.logger.init_args.save_dir")
     cfg = parser.parse_args([])
     assert cfg.trainer == Namespace(logger=False, save_dir=None)
@@ -236,6 +236,51 @@ def test_on_parse_subclass_target_in_union(parser):
     assert cfg.trainer.logger.init_args == Namespace(save_dir="logs")
 
 
+class TrainerLoggerList:
+    def __init__(
+        self,
+        save_dir: Optional[str] = None,
+        logger: List[Logger] = [],
+    ):
+        pass
+
+
+def test_on_parse_subclass_target_in_list(parser):
+    parser.add_class_arguments(TrainerLoggerList, "trainer")
+    parser.link_arguments("trainer.save_dir", "trainer.logger.init_args.save_dir")
+    cfg = parser.parse_args([])
+    assert cfg.trainer == Namespace(logger=[], save_dir=None)
+    cfg = parser.parse_args(["--trainer.save_dir=logs", "--trainer.logger=[Logger]"])
+    assert cfg.trainer.save_dir == "logs"
+    assert len(cfg.trainer.logger) == 1
+    assert cfg.trainer.logger[0].init_args == Namespace(save_dir="logs")
+    cfg = parser.parse_args(["--trainer.save_dir=logs", "--trainer.logger=[Logger, Logger]"])
+    assert len(cfg.trainer.logger) == 2
+    assert all(x.init_args == Namespace(save_dir="logs") for x in cfg.trainer.logger)
+
+
+class TrainerLoggerUnionList:
+    def __init__(
+        self,
+        save_dir: Optional[str] = None,
+        logger: Union[bool, Logger, List[Logger]] = False,
+    ):
+        pass
+
+
+def test_on_parse_subclass_target_in_union_list(parser):
+    parser.add_class_arguments(TrainerLoggerUnionList, "trainer")
+    parser.link_arguments("trainer.save_dir", "trainer.logger.init_args.save_dir")
+    cfg = parser.parse_args([])
+    assert cfg.trainer == Namespace(logger=False, save_dir=None)
+    cfg = parser.parse_args(["--trainer.save_dir=logs", "--trainer.logger=Logger"])
+    assert cfg.trainer.save_dir == "logs"
+    assert cfg.trainer.logger.init_args == Namespace(save_dir="logs")
+    cfg = parser.parse_args(["--trainer.save_dir=logs", "--trainer.logger=[Logger, Logger]"])
+    assert len(cfg.trainer.logger) == 2
+    assert all(x.init_args == Namespace(save_dir="logs") for x in cfg.trainer.logger)
+
+
 class ClassF:
     def __init__(
         self,