Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type variables set to <nothing> do not unify with more specific variables when used in a more specific context #6895

Open
bwo opened this issue May 23, 2019 · 2 comments

Comments

@bwo
Copy link
Contributor

bwo commented May 23, 2019

possibly related to #6613?

Here is some simplified code for an imaginary typed parser combinator library.

from typing import Callable, Optional, Tuple, Pattern, Union, TypeVar, Generic, Text

S = TypeVar('S')
T = TypeVar('T')
A = TypeVar('A')
B = TypeVar('B')


class ParserState(Generic[S, T]):
    pass


ParseResult = Optional[Tuple[A, ParserState[S, T]]]
Parser = Callable[[ParserState[S, T]], ParseResult[A, S, T]]


# the idea here is that `re` gives a parser that works with `Text` input and produces a bit of `Text` as output, with a bit of parser state `S` regarding which this parser is agnostic.
def re(pat):
    # type: (Union[Text, Pattern]) -> Parser[S, Text, Text]
    raise NotImplementedError


def then(p1, p2):
    # type: (Parser[S, T, A], Parser[S, T, B]) -> Parser[S, T, B]
    raise NotImplementedError


ws = re(u'\\s*')


# this fails to typecheck
def ws_then1(p):
    # type: (Parser[S, Text, A]) -> Parser[S, Text, A]
    return then(ws, p)


# this typechecks
def ws_then2(p):
    # type: (Parser[S, Text, A]) -> Parser[S, Text, A]
    return then(re(u'\\s*'), p)

The inferred type of ws is def (simpleparser.ParserState[<nothing>, builtins.str]) -> Union[Tuple[builtins.str, simpleparser.ParserState[<nothing>, builtins.str]], None], which I take to mean that all the type variables have been fully instantiated, with the unsupplied S being instantiated as <nothing>, which seems to be a kind of placeholder that unifies with nothing else. As a result, ws can't be used as-is in ws_then1:

simpleparser.py:33: error: Argument 1 to "then" has incompatible type "Callable[[ParserState[<nothing>, str]], Optional[Tuple[str, ParserState[<nothing>, str]]]]"; expected "Callable[[ParserState[S, str]], Optional[Tuple[str, ParserState[S, str]]]]"

But when its definition is inlined into ws_then2, it typechecks.

What I expected was that ws would have a revealed type like def [S] (simpleparser.ParserState[S`-1, builtins.str]) -> Union[Tuple[builtins.str, simpleparser.ParserState[S`-1, builtins.str]], None], i.e., that the variables in fact assigned <nothing> would remain general, or that <nothing> would unify with other type variables, or … something like that.

@bwo
Copy link
Contributor Author

bwo commented May 23, 2019

On doing some searching, judging from the still-open #3032. my expected behavior is the intended behavior? Specifically with reference to this comment:

  1. If a concrete type found for a type variable is <uninhabited>, but the constraint is not equality, leave the type variable unsubstituted, so it becomes a type parameter in the final result

@ilevkivskyi
Copy link
Member

This is how type variables work in mypy. A variable type can't bind type variables, i.e. type of ws can't be Parser[S, Text, Text]. Type inference for ws is clearly underspecified so S gets inferred as bottom type (this is what <nothing> is, a type with no values).

One problem I see here is that normally we should give an error in such cases asking for type annotation. The problem is that is_valid_inferred_type() is a bunch of ad-hoc isinstances() instead of a proper visitor. @JukkaL is this intentional?

Also in principle we can allow "standalone" types with free type variables for callable types, like in your case, but there is a separate place for this discussion, see #5738.

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

No branches or pull requests

2 participants