### 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
"""Returns the raw value"""
    return value
def check_type_bytes(value):
    """Convert a human-readable string value to bytes
    Raises :class:`TypeError` if unable to covert the value
    """
    try:
        return human_to_bytes(value)
    except ValueError:
        raise TypeError('%s cannot be converted to a Byte value' % type(value))
def check_type_bits(value):
    """Convert a human-readable string bits value to bits in integer.
    Example: ``check_type_bits('1Mb')`` returns integer 1048576.
    Raises :class:`TypeError` if unable to covert the value.
    """
    try:
        return human_to_bytes(value, isbits=True)
    except ValueError:
        raise TypeError('%s cannot be converted to a Bit value' % type(value))
def check_type_jsonarg(value):
    """Return a jsonified string. Sometimes the controller turns a json string
    into a dict/list so transform it back into json here
    Raises :class:`TypeError` if unable to covert the value
    """
    if isinstance(value, (text_type, binary_type)):
        return value.strip()
    elif isinstance(value, (list, tuple, dict)):
        return jsonify(value)
    raise TypeError('%s cannot be converted to a json string' % type(value))