Skip to content

stubtest regression for NewType in compiled extensions #19877

Open

Description

@QuLogic

Bug Report

For my compiled extension, I have introduced a NewType to prevent confusion with other int-like arguments. So I have something like this:

from typing import NewType

FooType = NewType('FooType', int)

def foo(x: FooType, /) -> FooType: ...

stubtest rightfully complains that this does not exist at runtime:

$ pip install --no-build-isolation . && stubtest newtype --ignore-disjoint-bases
error: newtype.newtype.FooType is not present at runtime
Stub: in file .../venv/lib64/python3.13/site-packages/newtype/newtype.pyi:3
<TypeInfo newtype.newtype.FooType>
Runtime:
MISSING

so I added it to the extension (as the Py_mod_exec function for the module):

static int
newtype_mod_exec(PyObject *module)
{
    PyObject *int_obj = (PyObject *)&PyLong_Type;
    return PyModule_AddObjectRef(module, "FooType", int_obj);
}

Strictly speaking, this is incorrect because NewType at runtime is supposed to produce a lambda that returns its argument directly. However, stubtest 1.17.1 and older are happy with it.

To Reproduce

Please see the simplified example repo here.

Expected Behavior

No complaints from stubtest.

Actual Behavior

stubtest 1.18.1 now complains about the signature:

$ pip install --no-build-isolation . && stubtest newtype --ignore-disjoint-bases
error: newtype.newtype.FooType.__init__ is inconsistent, runtime does not have parameter "item"
Stub: in file .../venv/lib64/python3.13/site-packages/newtype/newtype.pyi:-1
def (self: newtype.newtype.FooType, item: builtins.int)
Runtime:
def (self)

Your Environment

  • Mypy version used: 1.17.1/1.18.1
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: 3.13.7

Activity

  1. QuLogic commented on Sep 18, 2025

    @QuLogic
    Author

    In this branch, I have tried changing the runtime implementation into what's effectively an identity lamdba to match the docs of NewType:

    static PyObject *
    newtype_FooType(PyObject *self, PyObject *obj)
    {
        Py_XINCREF(obj);
        return obj;
    }

    but stubtest doesn't like that for a different reason:

    $ pip install --no-build-isolation . && stubtest newtype --ignore-disjoint-bases
    error: newtype.newtype.FooType is not a type
    Stub: in file .../venv/lib64/python3.13/site-packages/newtype/newtype.pyi:3
    <TypeInfo newtype.newtype.FooType>
    Runtime:
    def (object, /)
    

    As another option, I tried changing the runtime implementation to use typing.NewType also:

    static int
    newtype_mod_exec(PyObject *module)
    {
        PyObject *typing_mod = PyImport_ImportModule("typing");
        if (!typing_mod) {
            return -1;
        }
        PyObject *NewType = PyObject_GetAttrString(typing_mod, "NewType");
        if (!NewType) {
            return -1;
        }
    
        PyObject *int_obj = (PyObject *)&PyLong_Type;
        PyObject *FooType = PyObject_CallFunction(NewType, "sO", "FooType", int_obj);
        if (!FooType) {
            return -1;
        }
    
        int ret = PyModule_AddObjectRef(module, "FooType", FooType);
        Py_XDECREF(FooType);
        return ret;
    }

    but stubtest still doesn't like it:

    $ pip install --no-build-isolation . && stubtest newtype --ignore-disjoint-bases
    error: newtype.newtype.FooType is not a type
    Stub: in file .../venv/lib64/python3.13/site-packages/newtype/newtype.pyi:3
    <TypeInfo newtype.newtype.FooType>
    Runtime:
    def (x, /)
    
  2. QuLogic commented on Sep 18, 2025

    @QuLogic
    Author

    The original issue bisects to 13fa6c3 so I'm not sure if it's a problem in the int type hints or something else, though it would also be nice if the second option above worked, too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions