#!/usr/bin/env python3
# --------------------( LICENSE                           )--------------------
# Copyright (c) 2014-2020 Cecil Curry.
# See "LICENSE" for further details.

'''
**Beartype class utilities.**

This private submodule implements supplementary class-handling utility
functions required by various :mod:`beartype` facilities, including callables
generated by the :func:`beartype.beartype` decorator.

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar import _BeartypeUtilClassException

# ....................{ CONSTANTS                          }....................
_MODULE_NAME_BUILTINS = 'builtins'
'''
Fully-qualified name of the module declaring all **builtins** (i.e., objects
defined by the standard :mod:`builtins` module and thus globally available by
default *without* requiring explicit importation).
'''


_MODULE_NAME_BUILTINS_DOTTED = f'{_MODULE_NAME_BUILTINS}.'
'''
Fully-qualified name of the module declaring all builtins followed by a ``.``,
defined purely as a trivial optimization for the frequently accessed
:class:`beartype._decor._typistry.Beartypistry.__setitem__` dunder method.
'''

# ....................{ VALIDATORS                        }....................
def die_unless_class(cls: type) -> bool:
    '''
    Raise an exception unless the passed object is a class.

    Parameters
    ----------
    cls : type
        Object to be validated.

    Raises
    ----------
    _BeartypeUtilClassException
        If this object is *not* a class.
    '''

    # If this object is *NOT* a class, raise an exception.
    if not isinstance(cls, type):
        raise _BeartypeUtilClassException(f'{repr(cls)} not class.')

# ....................{ TESTERS                           }....................
def is_class_builtin(cls: type) -> bool:
    '''
    ``True`` only if the passed class is **builtin** (i.e., globally accessible
    C-based type requiring *no* explicit importation).

    Parameters
    ----------
    cls : type
        Class to be inspected.

    Returns
    ----------
    bool
        ``True`` only if this class is builtin.

    Raises
    ----------
    _BeartypeUtilClassException
        If this object is *not* a class.
    '''

    # Avoid circular import dependencies.
    from beartype._util.py.utilpymodule import (
        get_object_class_module_name_or_none)

    # If this object is *NOT* a class, raise an exception.
    die_unless_class(cls)
    # Else, this object is a class.

    # If the unqualified basename of this class is "NoneType", this is the
    # class of the "None" singleton. In this case, return false. Unlike all
    # other builtin classes, this class is globally inaccessible despite being
    # declared to be builtin:
    #     >>> import builtins
    #     >>> type(None).__name__
    #     'NoneType'
    #     >>> type(None).__module__
    #     'builtins'
    #     >>> NoneType
    #     NameError: name 'NoneType' is not defined   <---- this is balls
    #
    # This inconsistency almost certainly constitutes a bug in the CPython
    # interpreter, but it seems doubtful anyone else would see it that way and
    # almost certain everyone else would defend this edge case.
    #
    # We're *NOT* dying on that lonely hill. We obey the Law of Guido.
    if cls.__name__ == 'NoneType':
        return False
    # Else, this is *NOT* the class of the "None" singleton.

    # Fully-qualified name of the module defining this class if this class is
    # defined by a module *OR* "None" otherwise (i.e., if this class is
    # dynamically defined in-memory).
    cls_module_name = get_object_class_module_name_or_none(cls)

    # This return true only if this name is that of the "builtins" module
    # declaring all builtin classes.
    return cls_module_name == _MODULE_NAME_BUILTINS


def is_classname_builtin(classname: str) -> bool:
    '''
    ``True`` only if the passed fully-qualified classname is that of a
    **builtin type** (i.e., globally accessible C-based type requiring *no*
    explicit importation).

    Parameters
    ----------
    classname: str
        Classname to be inspected.

    Returns
    ----------
    bool
        ``True`` only if this classname is that of a builtin type.
    '''

    # Return true only if...
    return (
        # This classname is prefixed by "builtins." *AND*...
        classname.startswith(_MODULE_NAME_BUILTINS_DOTTED) and
        # This classname is *NOT* that of the type of the "None" singleton. See
        # is_class_builtin() for further commentary.
        classname != 'builtins.NoneType'
    )
