recursive these are loaded from the respective tsv files upon first use file id id info bool pywidestringlist true known structnone typeid partition 1 none if typeid startswith struct else typedefnone typeid none unsupported none if supported else true for typeid supported in knownindotc items id reason xxx we should be handling these through known tsv holds tuple of strings which we statically initialize uses of these should be const but we don t worry about it xxx we should normalize all cases to a single name e g kwlist currently the most common cache a copy the first time extracols unsupported for now we ignore known values i e extra xxx set up a filter we ignore functions and statements ignore forward declarations skip it assert isinstancetypedeps list typedeclaration typedeps members decl members or a forward decl has no members a forward decl has no members but that shouldn t surface here xxx fail xxx is this right keywords for pyargparsetupleandkeywords static char name static const char const name xxx these should be made const return failure reason checked checktypespecdecl typedecl types knowntypes if checked return checked if isimmutabledecl vartype return none if isfixedtypedecl vartype return fixedtype return mutable def checktypespecdecl typedecl types knowntypes typespec decl vartype typespec if typedecl is not none found types gettypedecl if found is none found knowntypes gettypedecl if found is not none extra found if extra is none xxx under what circumstances does this happen extra unsupported extra get unsupported if unsupported is fixedtype unsupported none return typespec if unsupported else none fall back to default known types if ispotstypespec return none elif issystemtypetypespec return none elif isfuncptrdecl vartype return none return typespec class analyzedinfo analyzed classonly def istargetcls raw if not super istargetraw return false if raw kind not in kinds return false return true classonly def parserawresultcls result extra typedecl extra super parserawresultresult extra if typedecl is none return none extra raise notimplementederror def initself item typedeclnone unsupportednone extra if unsupported in extra raise notimplementederroritem typedecl unsupported extra if not unsupported unsupported none elif isinstanceunsupported str typedeclaration unsupported unsupported elif unsupported is not fixedtype unsupported tupleunsupported self unsupported unsupported extra unsupported self unsupported for repr etc if self unsupported is none self supported none self supported true elif self unsupported is fixedtype if item kind is kind variable raise notimplementederroritem typedecl unsupported self supported true else self supported not self unsupported super inititem typedecl extra def renderself fmt line itemonlyfalse if fmt raw yield reprself return rendered super renderfmt itemonlyitemonly xxx if itemonly yield from rendered supported self supported if fmt in line brief rendered rendered parts if supported else if supported is false else rendered yield t joinparts elif fmt summary raise notimplementederrorfmt elif fmt full yield from rendered if supported yield f tsupported tsupported else raise notimplementederrorfmt class analysisinfo analysis itemclass analyzed classonly def builditemcls info resultnone if not isinstanceinfo declaration or info kind not in kinds raise notimplementederrorinfo result return super builditeminfo result def checkglobalsanalysis yield data failure ignored readignored for item in analysis if item kind kind variable continue if item supported continue if item id in ignored continue reason item unsupported if not reason reason elif not isinstancereason str if lenreason 1 reason reason reason f reason yield item f not supported reason 20titem storage or item vartype recursive these are loaded from the respective tsv files upon first use file id id info bool pywidestringlist true _known struct none typeid partition 1 none if typeid startswith struct else typedef none typeid none unsupported none if supported else true for typeid supported in _known_in_dot_c items id reason xxx we should be handling these through known tsv holds tuple of strings which we statically initialize uses of these should be const but we don t worry about it xxx we should normalize all cases to a single name e g kwlist currently the most common cache a copy the first time xxx extracols unsupported for now we ignore known values i e extra xxx set up a filter we ignore functions and statements ignore forward declarations skip it assert isinstance typedeps list typedeclaration typedeps members decl members or a forward decl has no members a forward decl has no members but that shouldn t surface here xxx fail xxx is this right keywords for pyarg_parsetupleandkeywords static char name static const char const name xxx these should be made const return failure reason xxx under what circumstances does this happen fall back to default known types classonly def _parse_raw_result cls result extra typedecl extra super _parse_raw_result result extra if typedecl is none return none extra raise notimplementederror for __repr__ etc self supported none xxx if itemonly yield from rendered yield data failure
import os.path

from c_common.clsutil import classonly
from c_parser.info import (
    KIND,
    Declaration,
    TypeDeclaration,
    Member,
    FIXED_TYPE,
)
from c_parser.match import (
    is_pots,
    is_funcptr,
)
from c_analyzer.match import (
    is_system_type,
    is_process_global,
    is_fixed_type,
    is_immutable,
)
import c_analyzer as _c_analyzer
import c_analyzer.info as _info
import c_analyzer.datafiles as _datafiles
from . import _parser, REPO_ROOT


_DATA_DIR = os.path.dirname(__file__)
KNOWN_FILE = os.path.join(_DATA_DIR, 'known.tsv')
IGNORED_FILE = os.path.join(_DATA_DIR, 'ignored.tsv')
NEED_FIX_FILE = os.path.join(_DATA_DIR, 'globals-to-fix.tsv')
KNOWN_IN_DOT_C = {
    'struct _odictobject': False,
    'PyTupleObject': False,
    'struct _typeobject': False,
    'struct _arena': True,  
    'struct _frame': False,
    'struct _ts': True,  
    'struct PyCodeObject': False,
    'struct _is': True,  
    'PyWideStringList': True,  
    
    'struct _dictkeysobject': False,
}

_KNOWN = {
    
    
}





_IGNORED = {
    
}


_OTHER_SUPPORTED_TYPES = {
    
    '_PyArg_Parser',
    
    'PyModuleDef',
    'PyModuleDef_Slot[]',
    'PyType_Spec',
    'PyType_Slot[]',
    'PyMethodDef',
    'PyMethodDef[]',
    'PyMemberDef[]',
    'PyGetSetDef[]',
    'PyNumberMethods',
    'PySequenceMethods',
    'PyMappingMethods',
    'PyAsyncMethods',
    'PyBufferProcs',
    'PyStructSequence_Field[]',
    'PyStructSequence_Desc',
}



_KWLIST_VARIANTS = [
    ('*', 'kwlist'),
    ('*', 'keywords'),
    ('*', 'kwargs'),
    ('Modules/_csv.c', 'dialect_kws'),
    ('Modules/_datetimemodule.c', 'date_kws'),
    ('Modules/_datetimemodule.c', 'datetime_kws'),
    ('Modules/_datetimemodule.c', 'time_kws'),
    ('Modules/_datetimemodule.c', 'timezone_kws'),
    ('Modules/_lzmamodule.c', 'optnames'),
    ('Modules/_lzmamodule.c', 'arg_names'),
    ('Modules/cjkcodecs/multibytecodec.c', 'incnewkwarglist'),
    ('Modules/cjkcodecs/multibytecodec.c', 'streamkwarglist'),
    ('Modules/socketmodule.c', 'kwnames'),
]

KINDS = frozenset((*KIND.TYPES, KIND.VARIABLE))


def read_known():
    if not _KNOWN:
        
        extracols = None  
        
        known = _datafiles.read_known(KNOWN_FILE, extracols, REPO_ROOT)
        
        types, _ = _datafiles.analyze_known(
            known,
            analyze_resolved=analyze_resolved,
        )
        _KNOWN.update(types)
    return _KNOWN.copy()


def write_known():
    raise NotImplementedError
    datafiles.write_known(decls, IGNORED_FILE, ['unsupported'], relroot=REPO_ROOT)


def read_ignored():
    if not _IGNORED:
        _IGNORED.update(_datafiles.read_ignored(IGNORED_FILE, relroot=REPO_ROOT))
        _IGNORED.update(_datafiles.read_ignored(NEED_FIX_FILE, relroot=REPO_ROOT))
    return dict(_IGNORED)


def write_ignored():
    raise NotImplementedError
    _datafiles.write_ignored(variables, IGNORED_FILE, relroot=REPO_ROOT)


def analyze(filenames, *,
            skip_objects=False,
            **kwargs
            ):
    if skip_objects:
        
        raise NotImplementedError

    known = read_known()

    decls = iter_decls(filenames)
    results = _c_analyzer.analyze_decls(
        decls,
        known,
        analyze_resolved=analyze_resolved,
    )
    analysis = Analysis.from_results(results)

    return analysis


def iter_decls(filenames, **kwargs):
    decls = _c_analyzer.iter_decls(
        filenames,
        
        kinds=KINDS,
        parse_files=_parser.parse_files,
        **kwargs
    )
    for decl in decls:
        if not decl.data:
            
            continue
        yield decl


def analyze_resolved(resolved, decl, types, knowntypes, extra=None):
    if decl.kind not in KINDS:
        
        return None

    typedeps = resolved
    if typedeps is _info.UNKNOWN:
        if decl.kind in (KIND.STRUCT, KIND.UNION):
            typedeps = [typedeps] * len(decl.members)
        else:
            typedeps = [typedeps]
    

    if extra is None:
        extra = {}
    elif 'unsupported' in extra:
        raise NotImplementedError((decl, extra))

    unsupported = _check_unsupported(decl, typedeps, types, knowntypes)
    extra['unsupported'] = unsupported

    return typedeps, extra


def _check_unsupported(decl, typedeps, types, knowntypes):
    if typedeps is None:
        raise NotImplementedError(decl)

    if decl.kind in (KIND.STRUCT, KIND.UNION):
        return _check_members(decl, typedeps, types, knowntypes)
    elif decl.kind is KIND.ENUM:
        if typedeps:
            raise NotImplementedError((decl, typedeps))
        return None
    else:
        return _check_typedep(decl, typedeps, types, knowntypes)


def _check_members(decl, typedeps, types, knowntypes):
    if isinstance(typedeps, TypeDeclaration):
        raise NotImplementedError((decl, typedeps))

    
    members = decl.members
    if not members:
        
        raise NotImplementedError(decl)
    if len(members) != len(typedeps):
        raise NotImplementedError((decl, typedeps))

    unsupported = []
    for member, typedecl in zip(members, typedeps):
        checked = _check_typedep(member, typedecl, types, knowntypes)
        unsupported.append(checked)
    if any(None if v is FIXED_TYPE else v for v in unsupported):
        return unsupported
    elif FIXED_TYPE in unsupported:
        return FIXED_TYPE
    else:
        return None


def _check_typedep(decl, typedecl, types, knowntypes):
    if not isinstance(typedecl, TypeDeclaration):
        if hasattr(type(typedecl), '__len__'):
            if len(typedecl) == 1:
                typedecl, = typedecl
    if typedecl is None:
        
        return 'typespec (missing)'
    elif typedecl is _info.UNKNOWN:
        if _has_other_supported_type(decl):
            return None
        
        return 'typespec (unknown)'
    elif not isinstance(typedecl, TypeDeclaration):
        raise NotImplementedError((decl, typedecl))

    if isinstance(decl, Member):
        return _check_vartype(decl, typedecl, types, knowntypes)
    elif not isinstance(decl, Declaration):
        raise NotImplementedError(decl)
    elif decl.kind is KIND.TYPEDEF:
        return _check_vartype(decl, typedecl, types, knowntypes)
    elif decl.kind is KIND.VARIABLE:
        if not is_process_global(decl):
            return None
        if _is_kwlist(decl):
            return None
        if _has_other_supported_type(decl):
            return None
        checked = _check_vartype(decl, typedecl, types, knowntypes)
        return 'mutable' if checked is FIXED_TYPE else checked
    else:
        raise NotImplementedError(decl)


def _is_kwlist(decl):
    
    
    
    for relpath, name in _KWLIST_VARIANTS:
        if decl.name == name:
            if relpath == '*':
                break
            assert os.path.isabs(decl.file.filename)
            relpath = os.path.normpath(relpath)
            if decl.file.filename.endswith(os.path.sep + relpath):
                break
    else:
        return False
    vartype = ''.join(str(decl.vartype).split())
    return vartype == 'char*[]'


def _has_other_supported_type(decl):
    if hasattr(decl, 'file') and decl.file.filename.endswith('.c.h'):
        assert 'clinic' in decl.file.filename, (decl,)
        if decl.name == '_kwtuple':
            return True
    vartype = str(decl.vartype).split()
    if vartype[0] == 'struct':
        vartype = vartype[1:]
    vartype = ''.join(vartype)
    return vartype in _OTHER_SUPPORTED_TYPES


def _check_vartype(decl, typedecl, types, knowntypes):
    
    checked = _check_typespec(decl, typedecl, types, knowntypes)
    if checked:
        return checked
    if is_immutable(decl.vartype):
        return None
    if is_fixed_type(decl.vartype):
        return FIXED_TYPE
    return 'mutable'


def _check_typespec(decl, typedecl, types, knowntypes):
    typespec = decl.vartype.typespec
    if typedecl is not None:
        found = types.get(typedecl)
        if found is None:
            found = knowntypes.get(typedecl)

        if found is not None:
            _, extra = found
            if extra is None:
                
                extra = {}
            unsupported = extra.get('unsupported')
            if unsupported is FIXED_TYPE:
                unsupported = None
            return 'typespec' if unsupported else None
    
    if is_pots(typespec):
        return None
    elif is_system_type(typespec):
        return None
    elif is_funcptr(decl.vartype):
        return None
    return 'typespec'


class Analyzed(_info.Analyzed):

    @classonly
    def is_target(cls, raw):
        if not super().is_target(raw):
            return False
        if raw.kind not in KINDS:
            return False
        return True

    
    
    
    
    
    

    def __init__(self, item, typedecl=None, *, unsupported=None, **extra):
        if 'unsupported' in extra:
            raise NotImplementedError((item, typedecl, unsupported, extra))
        if not unsupported:
            unsupported = None
        elif isinstance(unsupported, (str, TypeDeclaration)):
            unsupported = (unsupported,)
        elif unsupported is not FIXED_TYPE:
            unsupported = tuple(unsupported)
        self.unsupported = unsupported
        extra['unsupported'] = self.unsupported  
        if self.unsupported is None:
            
            self.supported = True
        elif self.unsupported is FIXED_TYPE:
            if item.kind is KIND.VARIABLE:
                raise NotImplementedError(item, typedecl, unsupported)
            self.supported = True
        else:
            self.supported = not self.unsupported
        super().__init__(item, typedecl, **extra)

    def render(self, fmt='line', *, itemonly=False):
        if fmt == 'raw':
            yield repr(self)
            return
        rendered = super().render(fmt, itemonly=itemonly)
        
        
        
        supported = self.supported
        if fmt in ('line', 'brief'):
            rendered, = rendered
            parts = [
                '+' if supported else '-' if supported is False else '',
                rendered,
            ]
            yield '\t'.join(parts)
        elif fmt == 'summary':
            raise NotImplementedError(fmt)
        elif fmt == 'full':
            yield from rendered
            if supported:
                yield f'\tsupported:\t{supported}'
        else:
            raise NotImplementedError(fmt)


class Analysis(_info.Analysis):
    _item_class = Analyzed

    @classonly
    def build_item(cls, info, result=None):
        if not isinstance(info, Declaration) or info.kind not in KINDS:
            raise NotImplementedError((info, result))
        return super().build_item(info, result)


def check_globals(analysis):
    
    ignored = read_ignored()
    for item in analysis:
        if item.kind != KIND.VARIABLE:
            continue
        if item.supported:
            continue
        if item.id in ignored:
            continue
        reason = item.unsupported
        if not reason:
            reason = '???'
        elif not isinstance(reason, str):
            if len(reason) == 1:
                reason, = reason
        reason = f'({reason})'
        yield item, f'not supported {reason:20}\t{item.storage or ""} {item.vartype}'