Skip to content

Soundness bugs with Callable[..., T] and partial #2288

Open
@drvink

Description

@drvink

See XXX comments for highlights.

Callable[..., T]:

#!/usr/bin/env python
from typing import Any, Callable, Optional, TypeVar
import socket, sys

class CodedException(Exception):
    code = None # type: int
class CommandLineError(CodedException): code = 3

T = TypeVar('T')

def of_string_werr(fc # type: Callable[..., T]
):
    # type: (...) -> Callable[[Callable[[], Any]], Callable[[str], Callable[..., T]]]
    def curry_handler(fh):
        # type: (Callable[[], Any]) -> Callable[[str], Callable[..., T]]
        def curry_arg(x): # type: (str) -> Callable[..., T]
            def mkf(*args, **kw):
                # type: (*Any, **Any) -> T
                try: return fc(x, *args, **kw)
                except ValueError: fh()
            return mkf
        return curry_arg
    return curry_handler

def int_of_string_werr(fh):
    # type: (Callable[[], Any]) -> Callable[[str], Callable[..., int]]
    return of_string_werr(int)(fh)

def fail(ex=None): # type: (Optional[CodedException]) -> None
    if ex:
        print('%s: error: %s' % (sys.argv[0], ex), file=sys.stderr)
        sys.exit(ex.code)
    else:
        print('doop', file=sys.stderr)
        sys.exit(CommandLineError.code)

def ff(): # type: () -> None
    fail(CommandLineError('port must be an integer'))

# XXX the buggy line; should be: port = int_of_string_werr(ff)('1234')()
port = int_of_string_werr(ff)('1234')
# reveal_type(port): def (*Any, **Any) -> builtins.int

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# XXX typechecks but fails at runtime with
# TypeError: an integer is required (got type function)
sock.connect(('blah', port))

partial:

#!/usr/bin/env python
from typing import Any, Callable, Optional, TypeVar
from functools import partial
import socket, sys

class CodedException(Exception):
    code = None # type: int
class CommandLineError(CodedException): code = 3

T = TypeVar('T')

def of_string_werr(fc # type: Callable[..., T]
):
    # type: (...) -> Callable[[Callable[[], Any]], Callable[[str], partial[T]]]
    def curry_handler(fh):
        # type: (Callable[[], Any]) -> Callable[[str], partial[T]]
        def curry_arg(x): # type: (str) -> partial[T]
            def mkf(*args, **kw):
                # type: (*Any, **Any) -> T
                try: return fc(x, *args, **kw)
                except ValueError: fh()
            return partial(mkf)
        return curry_arg
    return curry_handler

def int_of_string_werr(fh):
    # type: (Callable[[], Any]) -> Callable[[str], partial[int]]

    # XXX
    # Incompatible return value type (got Callable[[str], partial[object]],
    # expected Callable[[str], partial[int]])
    return of_string_werr(int)(fh)

def fail(ex=None): # type: (Optional[CodedException]) -> None
    if ex:
        print('%s: error: %s' % (sys.argv[0], ex), file=sys.stderr)
        sys.exit(ex.code)
    else:
        print('doop', file=sys.stderr)
        sys.exit(CommandLineError.code)

def ff(): # type: () -> None
    fail(CommandLineError('port must be an integer'))

# XXX the buggy line; should be: port = int_of_string_werr(ff)('999')()
port = int_of_string_werr(ff)('999')
# reveal_type(port): def (*Any, **Any) -> builtins.int

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# XXX typechecks but fails at runtime with
# TypeError: an integer is required (got type function)
sock.connect(('blah', port))

As an aside, Callable and especially partial are very clumsy to use with typing; the use of higher-order functions is greatly impaired. The fact that def is not Callable is not lambda is not partial is not any number of other things vs. having a single simple ML-style arrow type is unfortunate.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions