-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Conversation
Thanks for this fix!
Hmm. Kind of a shame to move all of these to the |
Alternatively -- these URLs are already pretty long. Maybe move this docs section to its own page? :-) |
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. |
Agreed we shouldn't have # in the URL.
…On May 22, 2017 4:36 PM, "Jesse Lord" ***@***.***> wrote:
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.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#3411 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/ACwrMrjoJS-J90TVUDPGir7Itko6-RD3ks5r8hvmgaJpZM4Ni_1w>
.
|
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 That'd shorten the URL, get the 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. |
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. |
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. |
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. |
Successfully splitting check*.test files on |
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 The change to split on |
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. |
(Update FTR for whatever it means about RTD: http://mypy.readthedocs.io/en/latest/variance is still a 404 page.) |
@quartox No experience needed! Take a look at docs/README.md for how to build and view changes you make in the docs. |
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): |
There was a problem hiding this comment.
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])
.
test-data/unit/check-varargs.test
Outdated
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] |
There was a problem hiding this comment.
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]
vsList[int]
), see my comment above - for situations where covariance will help, and where it will not (like
Dict[str, float]
vsDict[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: |
There was a problem hiding this comment.
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') |
There was a problem hiding this comment.
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 :-)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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) + |
There was a problem hiding this comment.
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.
@quartox It looks like several comments are still not addressed, please ping me back when you are ready. |
@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. |
@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]: |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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]:
test-data/unit/check-varargs.test
Outdated
@@ -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 | |||
|
|||
|
There was a problem hiding this comment.
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).
test-data/unit/check-varargs.test
Outdated
|
||
[case testInvariantTypeConfusingNames] | ||
from typing import TypeVar | ||
DictReader = TypeVar('DictReader') |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just class DictReader: pass
.
test-data/unit/check-inference.test
Outdated
@@ -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>] |
There was a problem hiding this comment.
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.
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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])
test-data/unit/check-varargs.test
Outdated
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 |
There was a problem hiding this comment.
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.
@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. |
There was a problem hiding this 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.
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.