### Summary

If the argument spec defines a dict variable as required:

- if the variable has no default in defaults/main.yml and is not provided in the playbook, argument spec validation will fail. That's OK.
- if the variable is defined as a string e.g. `''` or `'test-string'`, validation will fail. That's OK.
- if the variable is defines as None or `~`, validation will pass. That's NOT OK.

### Issue Type

Bug Report

### Component Name

argument_spec

### Ansible Version

```console
$ ansible --version
ansible [core 2.15.0.dev0] (devel 51bddd862b) last updated 2023/01/03 22:32:16 (GMT +000)
  config file = /home/nikos/projects/ansible-demo/ansible.cfg
  configured module search path = ['/home/nikos/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/nikos/projects/ansible/lib/ansible
  ansible collection location = /home/nikos/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/nikos/projects/ansible/bin/ansible
  python version = 3.10.8 (main, Nov  1 2022, 14:18:21) [GCC 12.2.0] (/usr/bin/python)
  jinja version = 3.1.2
  libyaml = True
```


### Configuration

```console
# if using a version older than ansible-core 2.12 you should omit the '-t all'
$ ansible-config dump --only-changed -t all
ANSIBLE_NOCOWS(/home/nikos/projects/ansible-demo/ansible.cfg) = True
CACHE_PLUGIN(/home/nikos/projects/ansible-demo/ansible.cfg) = ansible.builtin.json
CACHE_PLUGIN_CONNECTION(/home/nikos/projects/ansible-demo/ansible.cfg) = facts_cache
CONFIG_FILE() = /home/nikos/projects/ansible-demo/ansible.cfg
DEFAULT_EXECUTABLE(/home/nikos/projects/ansible-demo/ansible.cfg) = /bin/bash
DEFAULT_GATHERING(/home/nikos/projects/ansible-demo/ansible.cfg) = implicit
DEFAULT_HOST_LIST(/home/nikos/projects/ansible-demo/ansible.cfg) = ['/home/nikos/projects/ansible-demo/inventory.ini']
DEFAULT_MANAGED_STR(/home/nikos/projects/ansible-demo/ansible.cfg) = \nAnsible managed (do not edit, changes may be overwritten)
EDITOR(env: EDITOR) = nvim

CACHE:
=====

jsonfile:
________
```


### OS / Environment

Controller and target: Arch Linux

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
# role argument specs
---

argument_specs:
  main:
    options:
      dict_var:
        type: dict
        required: true
```

```
# playbook
---

- hosts: all
  roles:
    - myrole
  vars:
    dict_var: ~
```

### Expected Results

I expect argument validation to fail with a message that None type is not dict or could not be converted to dict.

### Actual Results

```console
Validation passes and role execution succeeds.
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct
"""Ensure all arguments have the requested values, and there are no stray arguments"""
    if errors is None:
        errors = AnsibleValidationErrorMultiple()
    for param, spec in argument_spec.items():
        choices = spec.get('choices')
        if choices is None:
            continue
        if isinstance(choices, (frozenset, KeysView, Sequence)) and not isinstance(choices, (binary_type, text_type)):
            if param in parameters:
                
                if isinstance(parameters[param], list):
                    diff_list = [item for item in parameters[param] if item not in choices]
                    if diff_list:
                        choices_str = ", ".join([to_native(c) for c in choices])
                        diff_str = ", ".join(diff_list)
                        msg = "value of %s must be one or more of: %s. Got no match for: %s" % (param, choices_str, diff_str)
                        if options_context:
                            msg = "{0} found in {1}".format(msg, " -> ".join(options_context))
                        errors.append(ArgumentValueError(msg))
                elif parameters[param] not in choices:
                    
                    
                    if parameters[param] == 'False':
                        overlap = BOOLEANS_FALSE.intersection(choices)
                        if len(overlap) == 1:
                            
                            (parameters[param],) = overlap
                    if parameters[param] == 'True':
                        overlap = BOOLEANS_TRUE.intersection(choices)