Skip to content

Add informative notes to invariant function arguments #3411

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

Merged
merged 31 commits into from
Aug 19, 2017

Conversation

quartox
Copy link
Contributor

@quartox quartox commented May 22, 2017

Fixes #1115 and #3352

Note that the check.test files must now use out since there is a # in the documentation url, which breaks the # E: syntax.

@gnprice gnprice self-assigned this May 22, 2017
@gnprice
Copy link
Collaborator

gnprice commented May 22, 2017

Thanks for this fix!

Note that the check.test files must now use out since there is a # in the documentation url, which breaks the # E: syntax.

Hmm. Kind of a shame to move all of these to the out form -- both because it's a little harder to read in itself (as the reader now loses the convenience of having the message right there on the line it's about), and because it makes the diff bigger and therefore harder to understand. I suspect this issue wouldn't be hard to fix -- want to do that first?

@gnprice
Copy link
Collaborator

gnprice commented May 22, 2017

Alternatively -- these URLs are already pretty long. Maybe move this docs section to its own page? :-)

@quartox
Copy link
Contributor Author

quartox commented May 22, 2017

Moving to a new page would do it. The current section is very far down the page, so difficult to expect the user to find it easily.
An alternative would be allow some form of escaping the # character in the check*.test files.

@gvanrossum
Copy link
Member

gvanrossum commented May 22, 2017 via email

@gnprice
Copy link
Collaborator

gnprice commented May 23, 2017

Even better than making a new page (though we can also do that too): I think we should make a redirect on RTD from some nice, shorter path (like variance?) to wherever we have it documented (like common_issues.html#invariance-vs-covariance).

That'd shorten the URL, get the # out -- and also give us the flexibility to move and rename that bit of the docs over time and preserve the links in existing versions of mypy.

Would want to put a comment in the docs source saying that the redirect should be updated when moved or renamed. We'd want a comment like that with or without the redirect, if we're going to link to it from error messages.

@gvanrossum
Copy link
Member

The RTD redirect sounds too fancy (and too RTD-specific). Let's just move the explanation of *variance to its own page, to be named variance.html. It's worth it.

@gnprice
Copy link
Collaborator

gnprice commented May 23, 2017

I just made such a redirect a few minutes ago as an experiment -- if I'm understanding the admin UI and the docs right, it ought to show up at http://mypy.readthedocs.io/en/latest/variance .

That URL 404s right now. Maybe it takes some time to propagate? Not super confidence-inspiring; there's no mention of that that I can see in the interface or the docs.

@gnprice
Copy link
Collaborator

gnprice commented May 23, 2017

I'm slightly concerned, but only slightly, about it being RTD-specific -- I assume that basically any reasonable way of hosting web pages now and in the future, especially one oriented to documentation, has some way of redirecting one page's URL to another. I do wish that the way to express the redirect were in the docs' source tree rather than in an RTD admin UI on the web.

@quartox
Copy link
Contributor Author

quartox commented May 23, 2017

Successfully splitting check*.test files on ' # ' (instead of '#') with all existing tests passing. This seems like an appropriate choice to me, but judge for yourself. Let me know if you settle on re-direct vs new html and we can fix the url.

@gnprice
Copy link
Collaborator

gnprice commented May 23, 2017

OK, it's been at least 10 minutes and that URL doesn't work, and also after some more discussion I'm convinced we don't want to rely on RTD's implementation of anything fancy for us.

Let's just move that section to a new page variance -- that'll be a very sensible page for us to have forever. Let's move that in this same PR.

The change to split on # is a nice cleanup in any case, I think; I'd be glad to see it as a separate tiny PR.

@quartox
Copy link
Contributor Author

quartox commented May 23, 2017

I just did a straight move from to a new file. I have no experience with sphinx so don't know how to test that it builds the docs correctly.

@gnprice
Copy link
Collaborator

gnprice commented May 23, 2017

(Update FTR for whatever it means about RTD: http://mypy.readthedocs.io/en/latest/variance is still a 404 page.)

@gnprice
Copy link
Collaborator

gnprice commented May 23, 2017

@quartox No experience needed! Take a look at docs/README.md for how to build and view changes you make in the docs.

@quartox
Copy link
Contributor Author

quartox commented May 23, 2017

@gnprice

mypy/messages.py Outdated
elif (arg_type.type.fullname() == 'builtins.dict' and
expected_type.type.fullname() == 'builtins.dict' and
arg_type.args[0].type == expected_type.args[0].type and
arg_type.args[1].type != expected_type.args[1].type):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is something important here missing (also for List): we should show the note only when using a covariant type will actually help, for example here:

x: List[int]
y: List[str]
x = y

we don't need to show any notes, since using Sequence will not help. So that instead arg_type.args[1].type != expected_type.args[1].type use if subtypes.is_subtype(arg_type.args[1], expected_type.args[1]).

f(a) # E: Argument 1 to "f" has incompatible type List[int]; expected List[Union[int, float]] \
# N: "List" is invariant --- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \
# N: Consider using "Sequence" instead, which is covariant
[builtins fixtures/list.pyi]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add tests:

  • for tricky names (like DictReader)
  • for unrelated list types (like List[str] vs List[int]), see my comment above
  • for situations where covariance will help, and where it will not (like Dict[str, float] vs Dict[str, int] and vice-versa).

In general, it is a good idea to add tests for all situations that appeared in the discussion of a PR.

mypy/messages.py Outdated
invariant_type = 'Dict'
covariant_suggestion = ('Consider using "Mapping" instead, '
'which is covariant in the value')
if invariant_type:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will crash, since invariant_type is not always defined.

mypy/messages.py Outdated
arg_type.args[1].type != expected_type.args[1].type):
invariant_type = 'Dict'
covariant_suggestion = ('Consider using "Mapping" instead, '
'which is covariant in the value')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"... covariant in the value type", if don't agree with a previous comment #3411 (comment), then please explain, just ignoring it is not a good option, I will not forget :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just missed it, thanks for catching.

Copy link
Contributor Author

@quartox quartox Aug 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am also not convinced that this message is clear to a novice user (which I expect is the intended audience for this type of note), but I can't think of a better concise message. I guess this assumes that the link is the most helpful part.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...at least value type suggests this is something related to types, not with the value itself (note that this error might appear in situations where list/dict literals are involved).

mypy/messages.py Outdated
@@ -992,6 +997,28 @@ def pretty_or(args: List[str]) -> str:
return ", ".join(quoted[:-1]) + ", or " + quoted[-1]


def invariance_notes(notes: List[str], arg_type: Type, expected_type: Type) -> List[str]:
"""Explain that the type is invariant and give notes for how to solve the issue."""
if isinstance(arg_type, Instance) and isinstance(expected_type, Instance):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I proposed to place this at the call site of this function not at the definition site, to avoid unnecessary calls.
(Also this will save you 4 spaces of indent.)

mypy/messages.py Outdated
'which is covariant in the value')
if invariant_type and covariant_suggestion:
notes.append(
'"{}" is invariant --- see '.format(invariant_type) +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also I proposed two dashes here instead of three.

@ilevkivskyi
Copy link
Member

@quartox It looks like several comments are still not addressed, please ping me back when you are ready.

@quartox
Copy link
Contributor Author

quartox commented Aug 19, 2017

@ilevkivskyi I have updated to address comments, but I am using the CI to check tests for me since my machine is very underpowered. I plan to address failing tests and we should be ready for more review after the tests are passing.

@quartox
Copy link
Contributor Author

quartox commented Aug 19, 2017

@ilevkivskyi the function definition now runs over 100 characters, what is the preferred way to move part to the next line?

mypy/messages.py Outdated
@@ -992,6 +998,31 @@ def pretty_or(args: List[str]) -> str:
return ", ".join(quoted[:-1]) + ", or " + quoted[-1]


def append_invariance_notes(notes: List[str], arg_type: Instance, expected_type: Instance) -> List[str]:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ilevkivskyi Sorry, this is an easier way to make this comment. What is the preferred way to move part of this definition to the next line?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like this:

def append_invariance_notes(notes: List[str], arg_type: Instance,
                            expected_type: Instance) -> List[str]:

@@ -592,3 +592,51 @@ class C:
def foo(self) -> None: pass
C().foo()
C().foo(1) # The decorator's return type says this should be okay


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One empty line between tests is enough (also below).


[case testInvariantTypeConfusingNames]
from typing import TypeVar
DictReader = TypeVar('DictReader')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need a type variable here, just make these normal (empty) classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I was struggling to figure out how to do this correctly. Do I need an __init__ method? Or is class DictReader enough?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just class DictReader: pass.

@@ -684,7 +684,7 @@ g('a')() # E: List[str] not callable
# to backtrack later) and defaults to T = <nothing>. The result is an
# awkward error message. Either a better error message, or simply accepting the
# call, would be preferable here.
g(['a']) # E: Argument 1 to "g" has incompatible type List[str]; expected List[<nothing>]
g(['a']) # E: Argument 1 to "g" has incompatible type List[str]; expected List[<nothing>]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the trailing space at the end of this line.

Copy link
Member

@ilevkivskyi ilevkivskyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, hopefully two last comments.

mypy/messages.py Outdated
expected_type.type.fullname() == 'builtins.dict' and
isinstance(arg_type.args[0], Instance) and
isinstance(expected_type.args[0], Instance) and
arg_type.args[0].type == expected_type.args[0].type and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this construction of three lines you can just use sametypes.is_same_types (if I remember the name correctly). This will allow other types, (like Union etc.) as well, and it is simpler.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean sametypes.is_same_types(arg_type.args[0], expected_type.args[0])

d = {'d': 1}
f(a) # E: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected Dict[str, Sequence[int]] \
# N: "Dict" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \
# N: Consider using "Mapping" instead, which is covariant in the value type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please format this with indents like this:

f(a) # E: Argument 1 to "f" has incompatible type Dict[str, List[int]]; expected Dict[str, Sequence[int]] \
     # N: "Dict" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance \
     # N: Consider using "Mapping" instead, which is covariant in the value type

here and elsewhere, it is much easier to read.

@quartox
Copy link
Contributor Author

quartox commented Aug 19, 2017

@ilevkivskyi Excellent, I just pushed the fixes. Thank you very much for your help. I know it was probably frustrating, but I learned a lot from the experience. Let me know if there are any more comments.

Copy link
Member

@ilevkivskyi ilevkivskyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! LGTM now, will merge when tests pass.

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

Successfully merging this pull request may close these issues.

4 participants