### 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
"""
    Helper function for :meth:`remove_values`.
    :arg value: The value to check for strings that need to be stripped
    :arg no_log_strings: set of strings which must be stripped out of any values
    :arg deferred_removals: List which holds information about nested
        containers that have to be iterated for removals.  It is passed into
        this function so that more entries can be added to it if value is
        a container type.  The format of each entry is a 2-tuple where the first
        element is the ``value`` parameter and the second value is a new
        container to copy the elements of ``value`` into once iterated.
    :returns: if ``value`` is a scalar, returns ``value`` with two exceptions:
        1. :class:`~datetime.datetime` objects which are changed into a string representation.
        2. objects which are in ``no_log_strings`` are replaced with a placeholder
           so that no sensitive data is leaked.
        If ``value`` is a container type, returns a new empty container.
    ``deferred_removals`` is added to as a side-effect of this function.
    .. warning:: It is up to the caller to make sure the order in which value
        is passed in is correct.  For instance, higher level containers need