type_hints.c
/*
* The PEP 484 type hints generator for SIP.
*
* Copyright (c) 2017 Riverbank Computing Limited <info@riverbankcomputing.com>
*
* This file is part of SIP.
*
* This copy of SIP is licensed for use under the terms of the SIP License
* Agreement. See the file LICENSE for more details.
*
* This copy of SIP may also used under the terms of the GNU General Public
* License v2 or v3 as published by the Free Software Foundation which can be
* found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
*
* SIP is supplied WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <stdio.h>
#include <string.h>
#include "sip.h"
/* Return a string referring to an object of any type. */
#define anyObject(pep484) ((pep484) ? "typing.Any" : "object")
static void pyiCompositeModule(sipSpec *pt, moduleDef *comp_mod, FILE *fp);
static void pyiModule(sipSpec *pt, moduleDef *mod, FILE *fp);
static void pyiTypeHintCode(codeBlockList *thc, int indent, FILE *fp);
static void pyiEnums(sipSpec *pt, moduleDef *mod, ifaceFileDef *scope,
ifaceFileList *defined, int indent, FILE *fp);
static void pyiVars(sipSpec *pt, moduleDef *mod, classDef *scope,
ifaceFileList *defined, int indent, FILE *fp);
static void pyiClass(sipSpec *pt, moduleDef *mod, classDef *cd,
ifaceFileList **defined, int indent, FILE *fp);
static void pyiMappedType(sipSpec *pt, moduleDef *mod, mappedTypeDef *mtd,
ifaceFileList **defined, int indent, FILE *fp);
static void pyiCtor(sipSpec *pt, moduleDef *mod, classDef *cd, ctorDef *ct,
int overloaded, int sec, ifaceFileList *defined, int indent, FILE *fp);
static void pyiCallable(sipSpec *pt, moduleDef *mod, memberDef *md,
overDef *overloads, int is_method, ifaceFileList *defined, int indent,
FILE *fp);
static void pyiProperty(sipSpec *pt, moduleDef *mod, propertyDef *pd,
int is_setter, memberDef *md, overDef *overloads,
ifaceFileList *defined, int indent, FILE *fp);
static void pyiOverload(sipSpec *pt, moduleDef *mod, overDef *od,
int overloaded, int is_method, int sec, ifaceFileList *defined,
int indent, int pep484, FILE *fp);
static void pyiPythonSignature(sipSpec *pt, moduleDef *mod, signatureDef *sd,
int need_self, int sec, ifaceFileList *defined, KwArgs kwargs,
int pep484, FILE *fp);
static int pyiArgument(sipSpec *pt, moduleDef *mod, argDef *ad, int arg_nr,
int out, int need_comma, int sec, int names, int defaults,
ifaceFileList *defined, KwArgs kwargs, int pep484, FILE *fp);
static void pyiType(sipSpec *pt, moduleDef *mod, argDef *ad, int out, int sec,
ifaceFileList *defined, int pep484, FILE *fp);
static void pyiTypeHint(sipSpec *pt, typeHintDef *thd, moduleDef *mod, int out,
ifaceFileList *defined, int pep484, FILE *fp);
static void pyiTypeHintNode(typeHintNodeDef *node, moduleDef *mod,
ifaceFileList *defined, int pep484, FILE *fp);
static void prIndent(int indent, FILE *fp);
static int separate(int first, int indent, FILE *fp);
static void prClassRef(classDef *cd, moduleDef *mod, ifaceFileList *defined,
int pep484, FILE *fp);
static void prEnumRef(enumDef *ed, moduleDef *mod, ifaceFileList *defined,
int pep484, FILE *fp);
static void prScopedEnumName(FILE *fp, enumDef *ed);
static int isDefined(ifaceFileDef *iff, classDef *cd, moduleDef *mod,
ifaceFileList *defined);
static int inIfaceFileList(ifaceFileDef *iff, ifaceFileList *defined);
static void parseTypeHint(sipSpec *pt, typeHintDef *thd, int out);
static int parseTypeHintNode(sipSpec *pt, int out, int top_level, char *start,
char *end, typeHintNodeDef **thnp);
static const char *typingModule(const char *name);
static typeHintNodeDef *lookupType(sipSpec *pt, char *name, int out);
static enumDef *lookupEnum(sipSpec *pt, const char *name, classDef *scope_cd,
mappedTypeDef *scope_mtd);
static mappedTypeDef *lookupMappedType(sipSpec *pt, const char *name);
static classDef *lookupClass(sipSpec *pt, const char *name,
classDef *scope_cd);
static classDef *getClassImplementation(sipSpec *pt, classDef *cd);
static mappedTypeDef *getMappedTypeImplementation(sipSpec *pt,
mappedTypeDef *mtd);
static void maybeAnyObject(const char *hint, int pep484, FILE *fp);
static void strip_leading(char **startp, char *end);
static void strip_trailing(char *start, char **endp);
static typeHintNodeDef *flatten_unions(typeHintNodeDef *nodes);
static typeHintNodeDef *copyTypeHintNode(sipSpec *pt, typeHintDef *thd,
int out);
/*
* Generate the .pyi file.
*/
void generateTypeHints(sipSpec *pt, moduleDef *mod, const char *pyiFile)
{
FILE *fp;
/* Generate the file. */
if ((fp = fopen(pyiFile, "w")) == NULL)
fatal("Unable to create file \"%s\"\n", pyiFile);
/* Write the header. */
fprintf(fp,
"# The PEP 484 type hints stub file for the %s module.\n"
"#\n"
"# Generated by SIP %s\n"
, mod->name
, sipVersion);
prCopying(fp, mod, "#");
fprintf(fp,
"\n"
"\n"
);
if (isComposite(mod))
pyiCompositeModule(pt, mod, fp);
else
pyiModule(pt, mod, fp);
fclose(fp);
}
/*
* Generate the type hints for a composite module.
*/
static void pyiCompositeModule(sipSpec *pt, moduleDef *comp_mod, FILE *fp)
{
moduleDef *mod;
for (mod = pt->modules; mod != NULL; mod = mod->next)
if (mod->container == comp_mod)
fprintf(fp, "from %s import *\n", mod->fullname->text);
}
/*
* Generate the type hints for an ordinary module.
*/
static void pyiModule(sipSpec *pt, moduleDef *mod, FILE *fp)
{
char *cp;
int first;
memberDef *md;
classDef *cd;
mappedTypeDef *mtd;
ifaceFileList *defined;
moduleListDef *mld;
/*
* Generate the imports. Note that we assume the super-types are the
* standard SIP ones.
*/
fprintf(fp,
"import typing\n"
"import sip\n"
);
first = TRUE;
for (mld = mod->imports; mld != NULL; mld = mld->next)
{
/* We lie about the indent because we only want one blank line. */
first = separate(first, 1, fp);
if ((cp = strrchr(mld->module->fullname->text, '.')) == NULL)
{
fprintf(fp, "import %s\n", mld->module->name);
}
else
{
*cp = '\0';
fprintf(fp, "from %s import %s\n", mld->module->fullname->text,
mld->module->name);
*cp = '.';
}
}
/*
* Generate any exported type hint code and any module-specific type hint
* code.
*/
pyiTypeHintCode(pt->exptypehintcode, 0, fp);
pyiTypeHintCode(mod->typehintcode, 0, fp);
/* Generate the types - global enums must be first. */
pyiEnums(pt, mod, NULL, NULL, 0, fp);
defined = NULL;
for (cd = pt->classes; cd != NULL; cd = cd->next)
{
classDef *impl;
if (cd->iff->module != mod)
continue;
if (isExternal(cd))
continue;
impl = getClassImplementation(pt, cd);
if (impl != NULL)
{
if (impl->no_typehint)
continue;
/* Only handle non-nested classes here. */
if (impl->ecd != NULL)
continue;
pyiClass(pt, mod, impl, &defined, 0, fp);
}
}
for (mtd = pt->mappedtypes; mtd != NULL; mtd = mtd->next)
{
mappedTypeDef *impl;
if (mtd->iff->module != mod)
continue;
impl = getMappedTypeImplementation(pt, mtd);
if (impl != NULL && impl->pyname != NULL)
pyiMappedType(pt, mod, impl, &defined, 0, fp);
}
pyiVars(pt, mod, NULL, defined, 0, fp);
first = TRUE;
for (md = mod->othfuncs; md != NULL; md = md->next)
if (md->slot == no_slot)
{
first = separate(first, 0, fp);
pyiCallable(pt, mod, md, mod->overs, FALSE, defined, 0, fp);
}
}
/*
* Generate handwritten type hint code.
*/
static void pyiTypeHintCode(codeBlockList *thc, int indent, FILE *fp)
{
while (thc != NULL)
{
int need_indent = TRUE;
const char *cp;
fprintf(fp, "\n");
for (cp = thc->block->frag; *cp != '\0'; ++cp)
{
if (need_indent)
{
need_indent = FALSE;
prIndent(indent, fp);
}
fprintf(fp, "%c", *cp);
if (*cp == '\n')
need_indent = TRUE;
}
thc = thc->next;
}
}
/*
* Generate the type hints for a class.
*/
static void pyiClass(sipSpec *pt, moduleDef *mod, classDef *cd,
ifaceFileList **defined, int indent, FILE *fp)
{
int first, no_body, nr_overloads;
classDef *nested;
ctorDef *ct;
memberDef *md;
propertyDef *pd;
separate(TRUE, indent, fp);
prIndent(indent, fp);
fprintf(fp, "class %s(", cd->pyname->text);
if (cd->supers != NULL)
{
classList *cl;
for (cl = cd->supers; cl != NULL; cl = cl->next)
{
if (cl != cd->supers)
fprintf(fp, ", ");
prClassRef(cl->cd, mod, *defined, TRUE, fp);
}
}
else if (cd->supertype != NULL)
{
fprintf(fp, "%s", cd->supertype->text);
}
else if (cd->iff->type == namespace_iface)
{
fprintf(fp, "sip.simplewrapper");
}
else
{
fprintf(fp, "sip.wrapper");
}
/* See if there is anything in the class body. */
nr_overloads = 0;
for (ct = cd->ctors; ct != NULL; ct = ct->next)
{
if (isPrivateCtor(ct))
continue;
if (ct->no_typehint)
continue;
if (!inDefaultAPI(pt, ct->api_range))
continue;
++nr_overloads;
}
no_body = (cd->typehintcode == NULL && nr_overloads == 0);
if (no_body)
{
overDef *od;
for (od = cd->overs; od != NULL; od = od->next)
{
if (isPrivate(od))
continue;
if (od->no_typehint)
continue;
if (inDefaultAPI(pt, od->api_range))
{
no_body = FALSE;
break;
}
}
}
if (no_body)
{
enumDef *ed;
for (ed = pt->enums; ed != NULL; ed = ed->next)
{
if (ed->no_typehint)
continue;
if (ed->ecd == cd)
{
no_body = FALSE;
break;
}
}
}
if (no_body)
{
for (nested = pt->classes; nested != NULL; nested = nested->next)
{
if (nested->no_typehint)
continue;
if (nested->ecd == cd)
{
no_body = FALSE;
break;
}
}
}
if (no_body)
{
varDef *vd;
for (vd = pt->vars; vd != NULL; vd = vd->next)
{
if (vd->no_typehint)
continue;
if (vd->ecd == cd)
{
no_body = FALSE;
break;
}
}
}
fprintf(fp, "):%s\n", (no_body ? " ..." : ""));
++indent;
pyiTypeHintCode(cd->typehintcode, indent, fp);
pyiEnums(pt, mod, cd->iff, *defined, indent, fp);
/* Handle any nested classes. */
for (nested = pt->classes; nested != NULL; nested = nested->next)
{
classDef *impl = getClassImplementation(pt, nested);
if (impl != NULL && impl->ecd == cd && !impl->no_typehint)
pyiClass(pt, mod, impl, defined, indent, fp);
}
pyiVars(pt, mod, cd, *defined, indent, fp);
first = TRUE;
for (ct = cd->ctors; ct != NULL; ct = ct->next)
{
int implicit_overloads, overloaded;
if (isPrivateCtor(ct))
continue;
if (ct->no_typehint)
continue;
if (!inDefaultAPI(pt, ct->api_range))
continue;
implicit_overloads = hasImplicitOverloads(&ct->pysig);
overloaded = (implicit_overloads || nr_overloads > 1);
first = separate(first, indent, fp);
pyiCtor(pt, mod, NULL, ct, overloaded, FALSE, *defined, indent, fp);
if (implicit_overloads)
pyiCtor(pt, mod, NULL, ct, overloaded, TRUE, *defined, indent, fp);
}
first = TRUE;
for (md = cd->members; md != NULL; md = md->next)
{
/*
* Ignore slots which can return Py_NotImplemented as code may be
* correctly handled elsewhere. We also have to include the sequence
* slots because they can't be distinguished from the number slots of
* the same name.
*/
if (isNumberSlot(md) || isInplaceNumberSlot(md) || isRichCompareSlot(md) || md->slot == concat_slot || md->slot == iconcat_slot || md->slot == repeat_slot || md->slot == irepeat_slot)
continue;
first = separate(first, indent, fp);
pyiCallable(pt, mod, md, cd->overs, TRUE, *defined, indent, fp);
}
for (pd = cd->properties; pd != NULL; pd = pd->next)
{
first = separate(first, indent, fp);
if (pd->get != NULL)
{
if ((md = findMethod(cd, pd->get)) != NULL)
{
pyiProperty(pt, mod, pd, FALSE, md, cd->overs, *defined,
indent, fp);
if (pd->set != NULL)
{
if ((md = findMethod(cd, pd->set)) != NULL)
pyiProperty(pt, mod, pd, TRUE, md, cd->overs, *defined,
indent, fp);
}
}
}
}
/*
* Keep track of what has been defined so that forward references are no
* longer required.
*/
appendToIfaceFileList(defined, cd->iff);
}
/*
* Generate the type hints for a mapped type.
*/
static void pyiMappedType(sipSpec *pt, moduleDef *mod, mappedTypeDef *mtd,
ifaceFileList **defined, int indent, FILE *fp)
{
int first, no_body;
memberDef *md;
/* See if there is anything in the mapped type body. */
no_body = (mtd->members == NULL);
if (no_body)
{
enumDef *ed;
for (ed = pt->enums; ed != NULL; ed = ed->next)
{
if (ed->no_typehint)
continue;
if (ed->emtd == mtd)
{
no_body = FALSE;
break;
}
}
}
if (!no_body)
{
separate(TRUE, indent, fp);
prIndent(indent, fp);
fprintf(fp, "class %s(sip.wrapper):\n", mtd->pyname->text);
++indent;
pyiEnums(pt, mod, mtd->iff, *defined, indent, fp);
first = TRUE;
for (md = mtd->members; md != NULL; md = md->next)
{
first = separate(first, indent, fp);
pyiCallable(pt, mod, md, mtd->overs, TRUE, *defined, indent, fp);
}
}
/*
* Keep track of what has been defined so that forward references are no
* longer required.
*/
appendToIfaceFileList(defined, mtd->iff);
}
/*
* Generate a ctor docstring.
*/
void dsCtor(sipSpec *pt, classDef *cd, ctorDef *ct, int sec, FILE *fp)
{
pyiCtor(pt, pt->module, cd, ct, FALSE, sec, NULL, 0, fp);
}
/*
* Generate an ctor type hint.
*/
static void pyiCtor(sipSpec *pt, moduleDef *mod, classDef *cd, ctorDef *ct,
int overloaded, int sec, ifaceFileList *defined, int indent, FILE *fp)
{
int a, need_comma;
if (overloaded)
{
prIndent(indent, fp);
fprintf(fp, "@typing.overload\n");
}
prIndent(indent, fp);
if (cd == NULL)
{
fprintf(fp, "def __init__(self");
need_comma = TRUE;
}
else
{
prScopedPythonName(fp, cd->ecd, cd->pyname->text);
fprintf(fp, "(");
need_comma = FALSE;
}
for (a = 0; a < ct->pysig.nrArgs; ++a)
need_comma = pyiArgument(pt, mod, &ct->pysig.args[a], a, FALSE,
need_comma, sec, TRUE, TRUE, defined, ct->kwargs, (cd == NULL),
fp);
fprintf(fp, (cd == NULL) ? ") -> None: ...\n" : ")");
}
/*
* Generate the APIs for all the enums in a scope.
*/
static void pyiEnums(sipSpec *pt, moduleDef *mod, ifaceFileDef *scope,
ifaceFileList *defined, int indent, FILE *fp)
{
enumDef *ed;
for (ed = pt->enums; ed != NULL; ed = ed->next)
{
enumMemberDef *emd;
if (ed->module != mod)
continue;
if (ed->no_typehint)
continue;
if (scope != NULL)
{
if ((ed->ecd == NULL || ed->ecd->iff != scope) && (ed->emtd == NULL || ed->emtd->iff != scope))
continue;
}
else if (ed->ecd != NULL || ed->emtd != NULL)
{
continue;
}
separate(TRUE, indent, fp);
if (ed->pyname != NULL)
{
prIndent(indent, fp);
fprintf(fp, "class %s(int): ...\n", ed->pyname->text);
}
for (emd = ed->members; emd != NULL; emd = emd->next)
{
if (emd->no_typehint)
continue;
prIndent(indent, fp);
fprintf(fp, "%s = ... # type: ", emd->pyname->text);
if (ed->pyname != NULL)
prEnumRef(ed, mod, defined, TRUE, fp);
else
fprintf(fp, "int");
fprintf(fp, "\n");
}
}
}
/*
* Generate the APIs for all the variables in a scope.
*/
static void pyiVars(sipSpec *pt, moduleDef *mod, classDef *scope,
ifaceFileList *defined, int indent, FILE *fp)
{
int first = TRUE;
varDef *vd;
for (vd = pt->vars; vd != NULL; vd = vd->next)
{
if (vd->module != mod)
continue;
if (vd->ecd != scope)
continue;
if (vd->no_typehint)
continue;
first = separate(first, indent, fp);
prIndent(indent, fp);
fprintf(fp, "%s = ... # type: ", vd->pyname->text);
pyiType(pt, mod, &vd->type, FALSE, FALSE, defined, TRUE, fp);
fprintf(fp, "\n");
}
}
/*
* Generate the type hints for a callable.
*/
static void pyiCallable(sipSpec *pt, moduleDef *mod, memberDef *md,
overDef *overloads, int is_method, ifaceFileList *defined, int indent,
FILE *fp)
{
int nr_overloads;
overDef *od;
/* Count the number of overloads. */
nr_overloads = 0;
for (od = overloads; od != NULL; od = od->next)
{
if (isPrivate(od))
continue;
if (od->common != md)
continue;
if (od->no_typehint)
continue;
if (!inDefaultAPI(pt, od->api_range))
continue;
++nr_overloads;
}
/* Handle each overload. */
for (od = overloads; od != NULL; od = od->next)
{
int implicit_overloads, overloaded;
if (isPrivate(od))
continue;
if (od->common != md)
continue;
if (od->no_typehint)
continue;
if (!inDefaultAPI(pt, od->api_range))
continue;
implicit_overloads = hasImplicitOverloads(&od->pysig);
overloaded = (implicit_overloads || nr_overloads > 1);
pyiOverload(pt, mod, od, overloaded, is_method, FALSE, defined, indent,
TRUE, fp);
if (implicit_overloads)
pyiOverload(pt, mod, od, overloaded, is_method, TRUE, defined,
indent, TRUE, fp);
}
}
/*
* Generate the type hints for a property.
*/
static void pyiProperty(sipSpec *pt, moduleDef *mod, propertyDef *pd,
int is_setter, memberDef *md, overDef *overloads,
ifaceFileList *defined, int indent, FILE *fp)
{
overDef *od;
/* Handle each overload. */
for (od = overloads; od != NULL; od = od->next)
{
if (isPrivate(od))
continue;
if (od->common != md)
continue;
if (od->no_typehint)
continue;
prIndent(indent, fp);
if (is_setter)
fprintf(fp, "@%s.setter\n", pd->name->text);
else
fprintf(fp, "@property\n");
prIndent(indent, fp);
fprintf(fp, "def %s", pd->name->text);
pyiPythonSignature(pt, mod, &od->pysig, TRUE, FALSE, defined,
od->kwargs, TRUE, fp);
fprintf(fp, ": ...\n");
break;
}
}
/*
* Generate the docstring for a single API overload.
*/
void dsOverload(sipSpec *pt, overDef *od, int is_method, int sec, FILE *fp)
{
pyiOverload(pt, pt->module, od, FALSE, is_method, sec, NULL, 0, FALSE, fp);
}
/*
* Generate the type hints for a single API overload.
*/
static void pyiOverload(sipSpec *pt, moduleDef *mod, overDef *od,
int overloaded, int is_method, int sec, ifaceFileList *defined,
int indent, int pep484, FILE *fp)
{
int need_self;
if (overloaded)
{
prIndent(indent, fp);
fprintf(fp, "@typing.overload\n");
}
if (pep484 && is_method && isStatic(od))
{
prIndent(indent, fp);
fprintf(fp, "@staticmethod\n");
}
prIndent(indent, fp);
fprintf(fp, "%s%s", (pep484 ? "def " : ""), od->common->pyname->text);
need_self = (is_method && !isStatic(od));
pyiPythonSignature(pt, mod, &od->pysig, need_self, sec, defined,
od->kwargs, pep484, fp);
if (pep484)
fprintf(fp, ": ...\n");
}
/*
* Generate a Python argument.
*/
static int pyiArgument(sipSpec *pt, moduleDef *mod, argDef *ad, int arg_nr,
int out, int need_comma, int sec, int names, int defaults,
ifaceFileList *defined, KwArgs kwargs, int pep484, FILE *fp)
{
int optional, use_optional;
if (isArraySize(ad))
return need_comma;
if (sec && (ad->atype == slotcon_type || ad->atype == slotdis_type))
return need_comma;
if (need_comma)
fprintf(fp, ", ");
optional = (defaults && ad->defval && !out);
/*
* We only show names for PEP 484 type hints and when they are part of the
* API.
*/
if (names)
names = (pep484 || kwargs == AllKwArgs || (kwargs == OptionalKwArgs && optional));
if (names && ad->atype != ellipsis_type)
{
if (ad->name != NULL)
fprintf(fp, "%s%s: ", ad->name->text,
(isPyKeyword(ad->name->text) ? "_" : ""));
else
fprintf(fp, "a%d: ", arg_nr);
}
use_optional = FALSE;
if (optional && pep484)
{
/* Assume pointers can be None unless specified otherwise. */
if (isAllowNone(ad) || (!isDisallowNone(ad) && ad->nrderefs > 0))
{
fprintf(fp, "typing.Optional[");
use_optional = TRUE;
}
}
pyiType(pt, mod, ad, out, sec, defined, pep484, fp);
if (names && ad->atype == ellipsis_type)
{
if (ad->name != NULL)
fprintf(fp, "%s%s", ad->name->text,
(isPyKeyword(ad->name->text) ? "_" : ""));
else
fprintf(fp, "a%d", arg_nr);
}
if (optional)
{
if (use_optional)
fprintf(fp, "]");
fprintf(fp, " = ");
if (pep484)
fprintf(fp, "...");
else
prDefaultValue(ad, TRUE, fp);
}
return TRUE;
}
/*
* Generate the default value of an argument.
*/
void prDefaultValue(argDef *ad, int in_str, FILE *fp)
{
/* Use any explicitly provided documentation. */
if (ad->typehint_value != NULL)
{
fprintf(fp, "%s", ad->typehint_value);
return;
}
/* Translate some special cases. */
if (ad->defval->next == NULL && ad->defval->vtype == numeric_value)
{
if (ad->nrderefs > 0 && ad->defval->u.vnum == 0)
{
fprintf(fp, "None");
return;
}
if (ad->atype == bool_type || ad->atype == cbool_type)
{
fprintf(fp, ad->defval->u.vnum ? "True" : "False");
return;
}
}
/* SIP v5 won't need this. */
prcode(fp, "%M");
generateExpression(ad->defval, in_str, fp);
prcode(fp, "%M");
}
/*
* Generate the Python representation of a type.
*/
static void pyiType(sipSpec *pt, moduleDef *mod, argDef *ad, int out, int sec,
ifaceFileList *defined, int pep484, FILE *fp)
{
const char *type_name;
typeHintDef *thd;
/* Use any explicit type hint unless the argument is constrained. */
thd = (out ? ad->typehint_out : (isConstrained(ad) ? NULL : ad->typehint_in));
if (thd != NULL)
{
pyiTypeHint(pt, thd, mod, out, defined, pep484, fp);
return;
}
/* For classes and mapped types we need the default implementation. */
if (ad->atype == class_type || ad->atype == mapped_type)
{
classDef *cd = ad->u.cd;
mappedTypeDef *mtd = ad->u.mtd;
getDefaultImplementation(pt, ad->atype, &cd, &mtd);
if (cd != NULL)
{
prClassRef(cd, mod, defined, pep484, fp);
}
else
{
/*
* This should never happen as it should have been picked up when
* generating code - but maybe we haven't been asked to generate
* code.
*/
fprintf(fp, anyObject(pep484));
}
return;
}
type_name = NULL;
switch (ad->atype)
{
case enum_type:
if (ad->u.ed->pyname != NULL)
prEnumRef(ad->u.ed, mod, defined, pep484, fp);
else
type_name = "int";
break;
case capsule_type:
type_name = scopedNameTail(ad->u.cap);
break;
case struct_type:
case void_type:
type_name = "sip.voidptr";
break;
case signal_type:
type_name = "QT_SIGNAL";
break;
case slot_type:
type_name = "QT_SLOT_QT_SIGNAL";
break;
case rxcon_type:
case rxdis_type:
if (sec)
{
type_name = (pep484 ? "typing.Callable[..., None]" : "Callable[..., None]");
}
else
{
/* The class should always be found. */
if (pt->qobject_cd != NULL)
prClassRef(pt->qobject_cd, mod, defined, pep484, fp);
else
type_name = anyObject(pep484);
}
break;
case qobject_type:
type_name = "QObject";
break;
case ustring_type:
/* Correct for Python v3. */
type_name = "bytes";
break;
case string_type:
case sstring_type:
case wstring_type:
case ascii_string_type:
case latin1_string_type:
case utf8_string_type:
type_name = isArray(ad) ? "bytes" : "str";
break;
case byte_type:
case sbyte_type:
case ubyte_type:
case ushort_type:
case uint_type:
case long_type:
case longlong_type:
case ulong_type:
case ulonglong_type:
case short_type:
case int_type:
case cint_type:
case ssize_type:
type_name = "int";
break;
case float_type:
case cfloat_type:
case double_type:
case cdouble_type:
type_name = "float";
break;
case bool_type:
case cbool_type:
type_name = "bool";
break;
case pyobject_type:
type_name = anyObject(pep484);
break;
case pytuple_type:
type_name = (pep484 ? "typing.Tuple" : "Tuple");
break;
case pylist_type:
type_name = (pep484 ? "typing.List" : "List");
break;
case pydict_type:
type_name = (pep484 ? "typing.Dict" : "Dict");
break;
case pycallable_type:
type_name = (pep484 ? "typing.Callable[..., None]" : "Callable[..., None]");
break;
case pyslice_type:
type_name = "slice";
break;
case pytype_type:
type_name = "type";
break;
case pybuffer_type:
type_name = "sip.Buffer";
break;
case ellipsis_type:
type_name = "*";
break;
case slotcon_type:
case anyslot_type:
type_name = "QT_SLOT";
break;
default:
type_name = anyObject(pep484);
}
if (type_name != NULL)
fprintf(fp, "%s", type_name);
}
/*
* Generate a scoped Python name.
*/
void prScopedPythonName(FILE *fp, classDef *scope, const char *pyname)
{
if (scope != NULL && !isHiddenNamespace(scope))
{
prScopedPythonName(fp, scope->ecd, NULL);
fprintf(fp, "%s.", scope->pyname->text);
}
if (pyname != NULL)
fprintf(fp, "%s", pyname);
}
/*
* Generate a Python signature.
*/
static void pyiPythonSignature(sipSpec *pt, moduleDef *mod, signatureDef *sd,
int need_self, int sec, ifaceFileList *defined, KwArgs kwargs,
int pep484, FILE *fp)
{
int void_return, need_comma, is_res, nr_out, a;
if (need_self)
{
fprintf(fp, "(self");
need_comma = TRUE;
}
else
{
fprintf(fp, "(");
need_comma = FALSE;
}
nr_out = 0;
for (a = 0; a < sd->nrArgs; ++a)
{
argDef *ad = &sd->args[a];
if (isOutArg(ad))
++nr_out;
if (!isInArg(ad))
continue;
need_comma = pyiArgument(pt, mod, ad, a, FALSE, need_comma, sec, TRUE,
TRUE, defined, kwargs, pep484, fp);
}
fprintf(fp, ")");
/* An empty type hint specifies a void return. */
if (sd->result.typehint_out != NULL)
void_return = (sd->result.typehint_out->raw_hint[0] == '\0');
else
void_return = FALSE;
is_res = !((sd->result.atype == void_type && sd->result.nrderefs == 0) ||
void_return);
if (is_res || nr_out > 0)
{
fprintf(fp, " -> ");
if ((is_res && nr_out > 0) || nr_out > 1)
fprintf(fp, "%sTuple[", (pep484 ? "typing." : ""));
if (is_res)
need_comma = pyiArgument(pt, mod, &sd->result, -1, TRUE, FALSE,
sec, FALSE, FALSE, defined, kwargs, pep484, fp);
else
need_comma = FALSE;
for (a = 0; a < sd->nrArgs; ++a)
{
argDef *ad = &sd->args[a];
if (isOutArg(ad))
/* We don't want the name in the result tuple. */
need_comma = pyiArgument(pt, mod, ad, -1, TRUE, need_comma,
sec, FALSE, FALSE, defined, kwargs, pep484, fp);
}
if ((is_res && nr_out > 0) || nr_out > 1)
fprintf(fp, "]");
}
else if (pep484)
{
fprintf(fp, " -> None");
}
}
/*
* Generate the required indentation.
*/
static void prIndent(int indent, FILE *fp)
{
while (indent--)
fprintf(fp, " ");
}
/*
* Generate a newline if not already done.
*/
static int separate(int first, int indent, FILE *fp)
{
if (first)
fprintf(fp, (indent ? "\n" : "\n\n"));
return FALSE;
}
/*
* Generate a class reference, including its owning module if necessary and
* handling forward references if necessary.
*/
static void prClassRef(classDef *cd, moduleDef *mod, ifaceFileList *defined,
int pep484, FILE *fp)
{
if (pep484)
{
/*
* We assume that an external class will be handled properly by some
* handwritten type hint code.
*/
int is_defined = (isExternal(cd) || isDefined(cd->iff, cd->ecd, mod, defined));
if (!is_defined)
fprintf(fp, "'");
if (cd->iff->module != mod)
fprintf(fp, "%s.", cd->iff->module->name);
prScopedPythonName(fp, cd->ecd, cd->pyname->text);
if (!is_defined)
fprintf(fp, "'");
}
else
{
prScopedPythonName(fp, cd->ecd, cd->pyname->text);
}
}
/*
* Generate an enum reference, including its owning module if necessary and
* handling forward references if necessary.
*/
static void prEnumRef(enumDef *ed, moduleDef *mod, ifaceFileList *defined,
int pep484, FILE *fp)
{
if (pep484)
{
int is_defined;
if (ed->ecd != NULL)
{
is_defined = isDefined(ed->ecd->iff, ed->ecd->ecd, mod, defined);
}
else if (ed->emtd != NULL)
{
is_defined = isDefined(ed->emtd->iff, NULL, mod, defined);
}
else
{
/* Global enums are defined early on. */
is_defined = TRUE;
}
if (!is_defined)
fprintf(fp, "'");
if (ed->module != mod)
fprintf(fp, "%s.", ed->module->name);
prScopedEnumName(fp, ed);
if (!is_defined)
fprintf(fp, "'");
}
else
{
prScopedEnumName(fp, ed);
}
}
/*
* Generate a scoped enum name.
*/
static void prScopedEnumName(FILE *fp, enumDef *ed)
{
if (ed->emtd != NULL)
fprintf(fp, "%s.%s", ed->emtd->pyname->text, ed->pyname->text);
else
prScopedPythonName(fp, ed->ecd, ed->pyname->text);
}
/*
* Check if a type has been defined.
*/
static int isDefined(ifaceFileDef *iff, classDef *scope, moduleDef *mod,
ifaceFileList *defined)
{
/* A type in another module would have been imported. */
if (iff->module != mod)
return TRUE;
if (!inIfaceFileList(iff, defined))
return FALSE;
/* Check all enclosing scopes have been defined as well. */
while (scope != NULL)
{
if (!inIfaceFileList(scope->iff, defined))
return FALSE;
scope = scope->ecd;
}
return TRUE;
}
/*
* Check if an interface file appears in a list of them.
*/
static int inIfaceFileList(ifaceFileDef *iff, ifaceFileList *defined)
{
while (defined != NULL)
{
if (defined->iff == iff)
return TRUE;
defined = defined->next;
}
return FALSE;
}
/*
* See if a signature has implicit overloads.
*/
int hasImplicitOverloads(signatureDef *sd)
{
int a;
for (a = 0; a < sd->nrArgs; ++a)
{
argDef *ad = &sd->args[a];
if (!isInArg(ad))
continue;
if (ad->atype == rxcon_type || ad->atype == rxdis_type)
return TRUE;
}
return FALSE;
}
/*
* Create a new type hint for a raw string.
*/
typeHintDef *newTypeHint(char *raw_hint)
{
typeHintDef *thd = sipMalloc(sizeof (typeHintDef));
thd->status = needs_parsing;
thd->raw_hint = raw_hint;
return thd;
}
/*
* Generate a type hint from a /TypeHint/ annotation.
*/
static void pyiTypeHint(sipSpec *pt, typeHintDef *thd, moduleDef *mod, int out,
ifaceFileList *defined, int pep484, FILE *fp)
{
parseTypeHint(pt, thd, out);
if (thd->root != NULL)
pyiTypeHintNode(thd->root, mod, defined, pep484, fp);
else
maybeAnyObject(thd->raw_hint, pep484, fp);
}
/*
* Generate a single node of a type hint.
*/
static void pyiTypeHintNode(typeHintNodeDef *node, moduleDef *mod,
ifaceFileList *defined, int pep484, FILE *fp)
{
switch (node->type)
{
case typing_node:
fprintf(fp, "%s%s", (pep484 ? "typing." : ""), node->u.name);
if (node->children != NULL)
{
int need_comma = FALSE;
typeHintNodeDef *thnd;
fprintf(fp, "[");
for (thnd = node->children; thnd != NULL; thnd = thnd->next)
{
if (need_comma)
fprintf(fp, ", ");
need_comma = TRUE;
pyiTypeHintNode(thnd, mod, defined, pep484, fp);
}
fprintf(fp, "]");
}
break;
case class_node:
prClassRef(node->u.cd, mod, defined, pep484, fp);
break;
case enum_node:
prEnumRef(node->u.ed, mod, defined, pep484, fp);
break;
case brackets_node:
fprintf(fp, "[]");
break;
case other_node:
maybeAnyObject(node->u.name, pep484, fp);
break;
}
}
/*
* Parse a type hint and update its status accordingly.
*/
static void parseTypeHint(sipSpec *pt, typeHintDef *thd, int out)
{
if (thd->status == needs_parsing)
{
thd->status = being_parsed;
parseTypeHintNode(pt, out, TRUE, thd->raw_hint,
thd->raw_hint + strlen(thd->raw_hint), &thd->root);
thd->status = parsed;
}
}
/*
* Recursively parse a type hint. Return FALSE if the parse failed.
*/
static int parseTypeHintNode(sipSpec *pt, int out, int top_level, char *start,
char *end, typeHintNodeDef **thnp)
{
char *cp, *name_start, *name_end;
int have_brackets = FALSE;
typeHintNodeDef *node, *children = NULL;
/* Assume there won't be a node. */
*thnp = NULL;
/* Find the name and any opening and closing bracket. */
strip_leading(&start, end);
name_start = start;
strip_trailing(start, &end);
name_end = end;
for (cp = start; cp < end; ++cp)
if (*cp == '[')
{
typeHintNodeDef **tail = &children;
/* The last character must be a closing bracket. */
if (end[-1] != ']')
return FALSE;
/* Find the end of any name. */
name_end = cp;
strip_trailing(name_start, &name_end);
for (;;)
{
char *pp;
int depth;
/* Skip the opening bracket or comma. */
++cp;
/* Find the next comma, if any. */
depth = 0;
for (pp = cp; pp < end; ++pp)
if (*pp == '[')
{
++depth;
}
else if (*pp == ']' && depth != 0)
{
--depth;
}
else if ((*pp == ',' || *pp == ']') && depth == 0)
{
typeHintNodeDef *child;
/* Recursively parse this part. */
if (!parseTypeHintNode(pt, out, FALSE, cp, pp, &child))
return FALSE;
if (child != NULL)
{
/*
* Append the child to the list of children. There
* might not be a child if we have detected a
* recursive definition.
*/
*tail = child;
tail = &child->next;
}
cp = pp;
break;
}
if (pp == end)
break;
}
have_brackets = TRUE;
break;
}
/* We must have a name unless we have empty brackets. */
if (name_start == name_end)
{
if (top_level && have_brackets && children == NULL)
return FALSE;
/* Return the representation of empty brackets. */
node = sipMalloc(sizeof (typeHintNodeDef));
node->type = brackets_node;
}
else
{
char saved;
const char *typing;
/* Isolate the name. */
saved = *name_end;
*name_end = '\0';
/* See if it is an object in the typing module. */
if ((typing = typingModule(name_start)) != NULL)
{
if (strcmp(typing, "Union") == 0)
{
/*
* If there are no children assume it is because they have been
* omitted.
*/
if (children == NULL)
return TRUE;
children = flatten_unions(children);
}
node = sipMalloc(sizeof (typeHintNodeDef));
node->type = typing_node;
node->u.name = typing;
node->children = children;
}
else
{
/* Search for the type. */
node = lookupType(pt, name_start, out);
}
*name_end = saved;
/* Only objects from the typing module can have brackets. */
if (typing == NULL && have_brackets)
return FALSE;
}
*thnp = node;
return TRUE;
}
/*
* Strip leading spaces from a string.
*/
static void strip_leading(char **startp, char *end)
{
char *start;
start = *startp;
while (start < end && start[0] == ' ')
++start;
*startp = start;
}
/*
* Strip trailing spaces from a string.
*/
static void strip_trailing(char *start, char **endp)
{
char *end;
end = *endp;
while (end > start && end[-1] == ' ')
--end;
*endp = end;
}
/*
* Look up an object in the typing module.
*/
static const char *typingModule(const char *name)
{
static const char *typing[] = {
"Any",
"Callable",
"Dict",
"Iterable",
"Iterator",
"List",
"Mapping",
"NamedTuple",
"Optional",
"Sequence",
"Set",
"Tuple",
"Union",
NULL
};
const char **np;
for (np = typing; *np != NULL; ++np)
if (strcmp(*np, name) == 0)
return *np;
return NULL;
}
/*
* Flatten an unions in a list of nodes.
*/
static typeHintNodeDef *flatten_unions(typeHintNodeDef *nodes)
{
typeHintNodeDef *head, **tailp, *thnd;
head = NULL;
tailp = &head;
for (thnd = nodes; thnd != NULL; thnd = thnd->next)
{
if (thnd->type == typing_node && strcmp(thnd->u.name, "Union") == 0)
{
typeHintNodeDef *child;
for (child = thnd->children; child != NULL; child = child->next)
{
*tailp = child;
tailp = &child->next;
}
}
else
{
/* Move this one to the new list. */
*tailp = thnd;
tailp = &thnd->next;
}
}
*tailp = NULL;
return head;
}
/*
* Look up a qualified Python type and return the corresponding node (or NULL
* if the type should be omitted because of a recursive definition).
*/
static typeHintNodeDef *lookupType(sipSpec *pt, char *name, int out)
{
char *sp, *ep;
classDef *scope_cd;
mappedTypeDef *scope_mtd;
typeHintDef *thd;
typeHintNodeDef *node;
/* Start searching at the global level. */
scope_cd = NULL;
scope_mtd = NULL;
sp = name;
ep = NULL;
while (*sp != '\0')
{
enumDef *ed;
/* Isolate the next part of the name. */
if ((ep = strchr(sp, '.')) != NULL)
*ep = '\0';
/* See if it's an enum. */
if ((ed = lookupEnum(pt, sp, scope_cd, scope_mtd)) != NULL)
{
/* Make sure we have used the whole name. */
if (ep == NULL)
{
node = sipMalloc(sizeof (typeHintNodeDef));
node->type = enum_node;
node->u.ed = ed;
return node;
}
/* There is some left so the whole lookup has failed. */
break;
}
/*
* If we have a mapped type scope then we must be looking for an enum,
* which we have failed to find.
*/
if (scope_mtd != NULL)
break;
if (scope_cd == NULL)
{
mappedTypeDef *mtd;
/*
* We are looking at the global level, so see if it is a mapped
* type.
*/
if ((mtd = lookupMappedType(pt, sp)) != NULL)
{
/*
* If we have used the whole name then the lookup has
* succeeded.
*/
if (ep == NULL)
{
thd = (out ? mtd->typehint_out : mtd->typehint_in);
if (thd != NULL && thd->status != being_parsed)
return copyTypeHintNode(pt, thd, out);
/*
* If we get here we have a recursively defined mapped type
* so we simply omit it.
*/
return NULL;
}
/* Otherwise this is the scope for the next part. */
scope_mtd = mtd;
}
}
if (scope_mtd == NULL)
{
classDef *cd;
/* If we get here then it must be a class. */
if ((cd = lookupClass(pt, sp, scope_cd)) == NULL)
break;
/* If we have used the whole name then the lookup has succeeded. */
if (ep == NULL)
{
thd = (out ? cd->typehint_out : cd->typehint_in);
if (thd != NULL && thd->status != being_parsed)
return copyTypeHintNode(pt, thd, out);
node = sipMalloc(sizeof (typeHintNodeDef));
node->type = class_node;
node->u.cd = cd;
return node;
}
/* Otherwise this is the scope for the next part. */
scope_cd = cd;
}
/* If we have run out of name then the lookup has failed. */
if (ep == NULL)
break;
/* Repair the name and go on to the next part. */
*ep++ = '.';
sp = ep;
}
/* Repair the name. */
if (ep != NULL)
*ep = '.';
/* Nothing was found. */
node = sipMalloc(sizeof (typeHintNodeDef));
node->type = other_node;
node->u.name = sipStrdup(name);
return node;
}
/*
* Copy the root node of a type hint.
*/
static typeHintNodeDef *copyTypeHintNode(sipSpec *pt, typeHintDef *thd,
int out)
{
typeHintNodeDef *node;
parseTypeHint(pt, thd, out);
if (thd->root == NULL)
return NULL;
node = sipMalloc(sizeof (typeHintNodeDef));
*node = *thd->root;
node->next = NULL;
return node;
}
/*
* Lookup an enum.
*/
static enumDef *lookupEnum(sipSpec *pt, const char *name, classDef *scope_cd,
mappedTypeDef *scope_mtd)
{
enumDef *ed;
for (ed = pt->enums; ed != NULL; ed = ed->next)
if (ed->pyname != NULL && strcmp(ed->pyname->text, name) == 0 && ed->ecd == scope_cd && ed->emtd == scope_mtd)
return ed;
return NULL;
}
/*
* Lookup a mapped type.
*/
static mappedTypeDef *lookupMappedType(sipSpec *pt, const char *name)
{
mappedTypeDef *mtd;
for (mtd = pt->mappedtypes; mtd != NULL; mtd = mtd->next)
if (mtd->pyname != NULL && strcmp(mtd->pyname->text, name) == 0)
{
mappedTypeDef *impl = getMappedTypeImplementation(pt, mtd);
if (impl != NULL)
return impl;
}
return NULL;
}
/*
* Lookup a class.
*/
static classDef *lookupClass(sipSpec *pt, const char *name, classDef *scope_cd)
{
classDef *cd;
for (cd = pt->classes; cd != NULL; cd = cd->next)
if (strcmp(cd->pyname->text, name) == 0 && cd->ecd == scope_cd && !isExternal(cd))
{
classDef *impl = getClassImplementation(pt, cd);
if (impl != NULL)
return impl;
}
return NULL;
}
/*
* Get the implementation (if there is one) for a type for the default API
* version.
*/
void getDefaultImplementation(sipSpec *pt, argType atype, classDef **cdp,
mappedTypeDef **mtdp)
{
classDef *cd;
mappedTypeDef *mtd;
ifaceFileDef *iff;
if (atype == class_type)
{
cd = *cdp;
mtd = NULL;
iff = cd->iff;
}
else
{
cd = NULL;
mtd = *mtdp;
iff = mtd->iff;
}
/* See if there is more than one implementation. */
if (iff->api_range != NULL)
{
int def_api;
cd = NULL;
mtd = NULL;
/* Find the default implementation. */
def_api = findAPI(pt, iff->api_range->api_name->text)->from;
for (iff = iff->first_alt; iff != NULL; iff = iff->next_alt)
{
apiVersionRangeDef *avd = iff->api_range;
if (avd->from > 0 && avd->from > def_api)
continue;
if (avd->to > 0 && avd->to <= def_api)
continue;
/* It's within range. */
if (iff->type == class_iface)
{
for (cd = pt->classes; cd != NULL; cd = cd->next)
if (cd->iff == iff)
break;
}
else
{
for (mtd = pt->mappedtypes; mtd != NULL; mtd = mtd->next)
if (mtd->iff == iff)
break;
}
break;
}
}
*cdp = cd;
*mtdp = mtd;
}
/*
* Return TRUE if a version range includes the default API.
*/
int inDefaultAPI(sipSpec *pt, apiVersionRangeDef *range)
{
int def_api;
/* Handle the trivial case. */
if (range == NULL)
return TRUE;
/* Get the default API. */
def_api = findAPI(pt, range->api_name->text)->from;
if (range->from > 0 && range->from > def_api)
return FALSE;
if (range->to > 0 && range->to <= def_api)
return FALSE;
return TRUE;
}
/*
* Get the class implementation (if there is one) of the given class according
* to the default version of any relevant API.
*/
static classDef *getClassImplementation(sipSpec *pt, classDef *cd)
{
mappedTypeDef *mtd;
getDefaultImplementation(pt, class_type, &cd, &mtd);
return cd;
}
/*
* Get the mapped type implementation (if there is one) of the given mapped
* type according to the default version of any relevant API.
*/
static mappedTypeDef *getMappedTypeImplementation(sipSpec *pt,
mappedTypeDef *mtd)
{
classDef *cd;
getDefaultImplementation(pt, mapped_type, &cd, &mtd);
return mtd;
}
/*
* Generate a hint taking into account that it may be any sort of object.
*/
static void maybeAnyObject(const char *hint, int pep484, FILE *fp)
{
fprintf(fp, "%s", (strcmp(hint, "Any") != 0 ? hint : anyObject(pep484)));
}