Repository: omni-us/jsonargparse

Issue:
Discard `init_args` after `class_path` change causes error
## 🐛 Bug report

When composing configs, it appears that overwritten class paths do not properly drop the old `init_args` namespace, causing an error when the new class does not have the same `init_args`.

Here is the error message:

```bash
$ main fit --config config_1.yaml --config configs_2.yaml
/unix/atlastracking/svanstroud/miniconda3/envs/salt/lib/python3.10/site-packages/jsonargparse/typehints.py:909: UserWarning: Due to class_path change from 'models.GATv2Attention' to 'models.ScaledDotProductAttention', discarding init_args: {'num_heads': 8, 'head_dim': 16, 'activation': Namespace(class_path='torch.nn.SiLU')}.
  warnings.warn(
usage: main [-h] [-c CONFIG] [--print_config[=flags]] {fit,validate,test,predict,tune} ...
main: error: Parser key "model": Problem with given class_path 'models.Tagger':
    - Parser key "gnn": Does not validate against any of the Union subtypes
      Subtypes: (<class 'torch.nn.modules.module.Module'>, <class 'NoneType'>)
      Errors:
        - Problem with given class_path 'models.Transformer':
          - Parser key "attention": Problem with given class_path 'models.ScaledDotProductAttention':
              - 'Configuration check failed :: No action for destination key "num_heads" to check its value.'
        - Expected a <class 'NoneType'>
      Given value type: <class 'jsonargparse.namespace.Namespace'>
      Given value: Namespace(class_path='models.Transformer', init_args=Namespace(embd_dim=128, num_heads=8, num_layers=8, attention=Namespace(class_path='models.ScaledDotProductAttention', init_args=Namespace(num_heads=8, head_dim=16, activation=Namespace(class_path='torch.nn.SiLU'))), activation='SiLU', residual=True, norm_layer='LayerNorm', dropout=0.1, out_proj=False))

```
### To reproduce

<!--
Please include a code snippet that reproduces the bug and the output that
running it gives. The following snippet templates might help:

1. Using the CLI function

```python
import jsonargparse

# Here define one or more functions or classes
def func1(param1: int, ...):
    ...

# Run the CLI providing the components
jsonargparse.CLI([func1, ...], error_handler=None)
```

2. Manually constructing a parser

```python
import jsonargparse

parser = jsonargparse.ArgumentParser(error_handler=None)
# Here add to the parser only argument(s) relevant to the problem

# If a yaml config is required, it can be included in the same snippet as follows:
import yaml
parser.add_argument('--config', action=jsonargparse.ActionConfigFile)
config = yaml.safe_dump({
    'key1': 'val1',
})

# Preferable that the command line arguments are given to the parse_args call
result = parser.parse_args([f'--config={config}', '--key2=val2', ...])

# If the problem is in the parsed result, print it to stdout
print(parser.dump(result))

# If the problem is in class instantiation
parser.instantiate_classes(result)
```
-->

### Expected behavior

The overwriting class should not be required to have the same `init_args` as the overwritten class. 
It would also be nice to have an option to suppress the warning about overwritten args.

### Environment

<!-- Fill in the list below. -->

- jsonargparse 4.18.0
- Python 3.10.6


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=212)

[![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=212&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=212&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=212&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=212&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=212&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=212&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=212&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=212&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=212&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=212&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=212&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=212&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=212) 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=212&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=212&metric=new_duplicated_lines_density&view=list)


codecov-commenter: # [Codecov](https://codecov.io/gh/omni-us/jsonargparse/pull/212?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) Report
Base: **100.00**% // Head: **100.00**% // No change to project coverage :thumbsup:
> Coverage data is based on head [(`955eafb`)](https://codecov.io/gh/omni-us/jsonargparse/pull/212?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) compared to base [(`9bbbfbe`)](https://codecov.io/gh/omni-us/jsonargparse/commit/9bbbfbe4c83b40517634d233989b77d0e9a0477e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).
> Patch coverage: 100.00% of modified lines in pull request are covered.

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


```diff
@@            Coverage Diff            @@
##            master      #212   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           18        18           
  Lines         4651      4663   +12     
=========================================
+ Hits          4651      4663   +12     
```

| Flag | Coverage Δ | |
|---|---|---|
| py3.10 | `91.67% <100.00%> (+0.02%)` | :arrow_up: |
| py3.10_all | `99.93% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.6 | `91.33% <100.00%> (+0.02%)` | :arrow_up: |
| py3.6_all | `99.65% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.7 | `91.35% <100.00%> (+0.02%)` | :arrow_up: |
| py3.7_all | `99.84% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.8 | `91.65% <100.00%> (+0.02%)` | :arrow_up: |
| py3.8_all | `99.91% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.9 | `91.67% <100.00%> (+0.02%)` | :arrow_up: |
| py3.9_all | `99.91% <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.

| [Impacted Files](https://codecov.io/gh/omni-us/jsonargparse/pull/212?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | Coverage Δ | |
|---|---|---|
| [jsonargparse/namespace.py](https://codecov.io/gh/omni-us/jsonargparse/pull/212/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL25hbWVzcGFjZS5weQ==) | `100.00% <100.00%> (ø)` | |
| [jsonargparse/typehints.py](https://codecov.io/gh/omni-us/jsonargparse/pull/212/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL3R5cGVoaW50cy5weQ==) | `100.00% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/omni-us/jsonargparse/pull/212?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](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).

samvanstroud: Thanks a lot @mauvilsa. I am still having issues when overwriting one class with another. I am going from a submodule with `init_args` provided to one without.

```bash
.../lib/python3.10/site-packages/jsonargparse/typehints.py:909: UserWarning: Due to class_path change from 'models.GATv2Attention' to 'models.ScaledDotProductAttention', discarding init_args: {'num_heads': 8, 'head_dim': 16}.
  warnings.warn(
usage: main [-h] [-c CONFIG] [--print_config[=flags]] {fit,validate,test,predict,tune} ...
main: error: Parser key "model.model": Problem with given class_path 'models.MyModel':
    - Parser key "gnn": Does not validate against any of the Union subtypes
      Subtypes: (<class 'torch.nn.modules.module.Module'>, <class 'NoneType'>)
      Errors:
        - Problem with given class_path 'models.Transformer':
          - Parser key "attention": Problem with given class_path 'models.ScaledDotProductAttention':
              - 'Configuration check failed :: No action for destination key "num_heads" to check its value.'
        - Expected a <class 'NoneType'>
      Given value type: <class 'jsonargparse.namespace.Namespace'>
      Given value: Namespace(class_path='models.Transformer', init_args=Namespace(embed_dim=128, num_heads=8, num_layers=6, attention=Namespace(class_path='models.ScaledDotProductAttention', init_args=Namespace(num_heads=8, head_dim=16)), activation='SiLU', out_dim=128, residual=True, norm_layer='LayerNorm', dropout=0.1, out_proj=False))
```
mauvilsa: @samvanstroud could you please create a minimal reproduction script. With just the error message it is difficult for me to understand what exactly the issue is and how your case differs to the unit test that I wrote.
samvanstroud: Here is a code

```python
# test.py
import pytorch_lightning as pl
import torch.nn as nn
from pytorch_lightning.cli import LightningCLI
from pytorch_lightning.demos.boring_classes import BoringDataModule, DemoModel


class SubA(nn.Module):
    def __init__(self):
        super().__init__()

    def forward(x):
        return x


class SubB(nn.Module):
    def __init__(self, arg: int):
        super().__init__()

        self.arg = arg

    def forward(x):
        return x


class MyModel(DemoModel):
    def __init__(self, model: nn.Module):
        super().__init__()
        self.model = model


def cli_main():
    cli = LightningCLI(MyModel, BoringDataModule)


if __name__ == "__main__":
    cli_main()
```

This runs fine with 
```bash
python test.py fit --trainer.fast_dev_run 2 --model.model SubB --model.model.arg 1 --model.model SubA
```

But, if we create two yaml files
```yaml
# A.yaml
model:
    model:
        class_path: SubA
```
and 
```yaml
# B.yaml
model:
    model:
        class_path: SubB
        init_args:
            arg: 1
```
then 
```bash
python test.py fit --trainer.fast_dev_run 2 --config A.yaml --config B.yaml
```
runs fine, but
```bash
python test.py fit --trainer.fast_dev_run 2 --config B.yaml --config A.yaml
```
errors with 
```bash
.../python3.10/site-packages/jsonargparse/typehints.py:909: UserWarning: Due to class_path change from '__main__.SubB' to '__main__.SubA', discarding init_args: {'arg': 1}.
  warnings.warn(
usage: test.py [-h] [-c CONFIG] [--print_config[=flags]] {fit,validate,test,predict,tune} ...
test.py: error: Parser key "model.model": Problem with given class_path '__main__.SubA':
    - 'Configuration check failed :: No action for destination key "arg" to check its value.'
```
samvanstroud: Also, would you consider the possibility to suppress the warning? e.g.

`.../python3.10/site-packages/jsonargparse/typehints.py:909: UserWarning: Due to class_path change from '__main__.SubB' to '__main__.SubA', discarding init_args: {'arg': 1}.`
mauvilsa: > Also, would you consider the possibility to suppress the warning?

In some time I will convert this to a log at debug level, since I need this to be visible somehow to identify problems like this one. But I am not doing this right now because that requires more work. For the time being you can suppress the warning how any warning in python is suppressed.
mauvilsa: @samvanstroud thank you for the code snippet. I tired it all worked correctly. Are you sure you tested with the branch from this pull request?
samvanstroud: Ah apologies, looks like pip did not actually perform the install since the version requirement was already satisfied. After uninstalling and re-installing everything is working, thanks a lot!
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=212)

[![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=212&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=212&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=212&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=212&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=212&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=212&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=212&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=212&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=212&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=212&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=212&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=212&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=212) 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=212&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=212&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 23e37a4..5692d1a 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -28,6 +28,8 @@ Fixed
 - List type with empty list default causes failure `PyLaia#48
   <https://github.com/jpuigcerver/PyLaia/issues/48>`__.
 - Pure dataclass instance default being considered as a subclass type.
+- Discard ``init_args`` after ``class_path`` change causes error `#205
+  <https://github.com/omni-us/jsonargparse/issues/205>`__.
 
 
 v4.18.0 (2022-11-29)
diff --git a/jsonargparse/namespace.py b/jsonargparse/namespace.py
index cc62bc7..affd615 100644
--- a/jsonargparse/namespace.py
+++ b/jsonargparse/namespace.py
@@ -244,7 +244,7 @@ class Namespace(argparse.Namespace):
             if isinstance(val, Namespace):
                 if branches:
                     yield key, val
-                for subkey, subval in val.items():
+                for subkey, subval in val.items(branches):
                     yield key+'.'+del_clash_mark(subkey), subval
             else:
                 yield key, val
diff --git a/jsonargparse/typehints.py b/jsonargparse/typehints.py
index 064c6d1..1edc7c8 100644
--- a/jsonargparse/typehints.py
+++ b/jsonargparse/typehints.py
@@ -295,8 +295,12 @@ class ActionTypeHint(Action):
 
     @staticmethod
     def discard_init_args_on_class_path_change(parser_or_action, prev_cfg, cfg):
+        if isinstance(prev_cfg, dict):
+            return
         keys = list(prev_cfg.keys(branches=True))
-        for key in keys:
+        num = 0
+        while num < len(keys):
+            key = keys[num]
             prev_val = prev_cfg.get(key)
             val = cfg.get(key)
             if is_subclass_spec(prev_val) and is_subclass_spec(val):
@@ -305,6 +309,14 @@ class ActionTypeHint(Action):
                     action = _find_action(parser_or_action, key)
                 if isinstance(action, ActionTypeHint):
                     discard_init_args_on_class_path_change(action, prev_val, val)
+                    prev_sub_cfg = prev_val.get('init_args')
+                    if prev_sub_cfg:
+                        sub_add_kwargs = getattr(action, 'sub_add_kwargs', {})
+                        subparser = ActionTypeHint.get_class_parser(val['class_path'], sub_add_kwargs)
+                        sub_cfg = val.get('init_args', Namespace())
+                        ActionTypeHint.discard_init_args_on_class_path_change(subparser, prev_sub_cfg, sub_cfg)
+                    keys = keys[:num+1] + [k for k in keys[num+1:] if not k.startswith(key+'.')]
+            num += 1
 
 
     @staticmethod


Test patch:
diff --git a/jsonargparse_tests/test_link_arguments.py b/jsonargparse_tests/test_link_arguments.py
index ca1d901..9a71eee 100755
--- a/jsonargparse_tests/test_link_arguments.py
+++ b/jsonargparse_tests/test_link_arguments.py
@@ -230,6 +230,9 @@ class LinkArgumentsTests(unittest.TestCase):
             cfg = parser.parse_args(['--a='+json.dumps(a_value), '--c='+json.dumps(c_value)])
             self.assertEqual(cfg.a.init_args.a1, c_value)
             self.assertEqual(cfg.a.init_args.a2, c_value)
+            init = parser.instantiate_classes(cfg)
+            self.assertIsInstance(init.a, ClassA)
+            self.assertIsInstance(init.c, Calendar)
 
 
     def test_link_arguments_on_parse_within_subcommand(self):
diff --git a/jsonargparse_tests/test_signatures.py b/jsonargparse_tests/test_signatures.py
index 49b7f71..c0364ab 100755
--- a/jsonargparse_tests/test_signatures.py
+++ b/jsonargparse_tests/test_signatures.py
@@ -9,6 +9,7 @@ from calendar import Calendar, January  # type: ignore
 from contextlib import redirect_stderr, redirect_stdout
 from enum import Enum
 from io import StringIO
+from pathlib import Path
 from typing import Any, Dict, List, Optional, Tuple, Union
 
 import yaml
@@ -1309,6 +1310,59 @@ class SignaturesConfigTests(TempDirTestCase):
         self.assertEqual(cfg.fit.model.foo, 123)
 
 
+    def test_config_nested_discard_init_args(self):
+        class Base:
+            def __init__(self, b: float = 0.5):
+                pass
+
+        class Sub1(Base):
+            def __init__(self, s1: str = 'x', **kwargs):
+                super().__init__(**kwargs)
+
+        class Sub2(Base):
+            def __init__(self, s2: int = 3, **kwargs):
+                super().__init__(**kwargs)
+
+        class Main:
+            def __init__(self, sub: Base = lazy_instance(Sub1)) -> None:
+                self.sub = sub
+
+        with mock_module(Base, Sub1, Sub2, Main) as module:
+            subconfig = {
+                'sub': {
+                    'class_path': f'{module}.Sub2',
+                    'init_args': {'s2': 4},
+                }
+            }
+
+            for subtest in ['class', 'subclass']:
+                with self.subTest(subtest), warnings.catch_warnings(record=True) as w:
+                    parser = ArgumentParser(error_handler=None)
+                    parser.add_argument('--config', action=ActionConfigFile)
+
+                    if subtest == 'class':
+                        config = {'main': subconfig}
+                        parser.add_class_arguments(Main, 'main')
+                    else:
+                        config = {
+                            'main': {
+                                'class_path': f'{module}.Main',
+                                'init_args': subconfig,
+                            }
+                        }
+                        parser.add_subclass_arguments(Main, 'main')
+                        parser.set_defaults(main=lazy_instance(Main))
+
+                    config_path = Path('config.yaml')
+                    config_path.write_text(yaml.safe_dump(config))
+
+                    cfg = parser.parse_args([f'--config={config_path}'])
+                    init = parser.instantiate_classes(cfg)
+                    self.assertIsInstance(init.main, Main)
+                    self.assertIsInstance(init.main.sub, Sub2)
+                    self.assertIn("discarding init_args: {'s1': 'x'}", str(w[0].message))
+
+
 @dataclasses.dataclass(frozen=True)
 class MyDataClassA:
     """MyDataClassA description