-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Pr/strict optional #1562
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
Pr/strict optional #1562
Conversation
Any chance you can resolve the conflicts? |
@@ -0,0 +1 @@ | |||
STRICT_OPTIONAL = False |
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 don't think an adjective make a good module name. Can you make it a noun, e.g. 'experiments'?
Now allows non-Optional class variables to be initialized to None, though it doesn't check that they are set to something else in init (or a method init calls). |
I've figured out one of the mypy-on-mypy errors this introduces, though it's not entirely clear to me what the best solution is. Basically, the problem boils down to mypy inferring member variable types from isinstance calls but not from assignments. To be concrete: This results in an error: from typing import Union
class C:
x = "" # type: Union[int, str]
def f(self) -> None:
self.x = 1
self.x + 1 # E: Unsupported operand types for + ("Union[int, str]" and "int") But this works: def f(x: Union[int, str]) -> None:
x = 1
x + 1 And so does this: from typing import Union
class C:
x = "" # type: Union[int, str]
def f(self) -> None:
if isinstance(self.x, int):
self.x + 1 This problem was exposed because we have code that looks like this: class C:
x = ""
def f(self) -> None:
if self.x is not None:
return
self.x = "foo"
self.x.capitalize() # E: None has no attribute "capitalize" With this PR, mypy now understands that returning when It's not generally sound to assume that instance variables don't change their type within a function after assignment (within the constraints of their declared type), because method calls might mutate them. So it's not obvious what the best fix here is. |
really not convinced that the line length is set right
On further investigation, not inferring local types from member assignment appears to not be intended behavior, but rather the result of a bug. Here's what I've traced so far:
|
@@ -482,6 +484,9 @@ def parse_function(self, no_type_checks: bool=False) -> FuncDef: | |||
if is_error: | |||
return None | |||
|
|||
if typ and isinstance(typ.ret_type, UnboundType): | |||
typ.ret_type.ret_type = True |
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 is confusing; maybe name the new field of UnboundType
is_ret_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.
Yeah, that's much better.
I assume we intend to support non-strict optional checking for the foreseeable future; if not then perhaps the following comments aren't relevant. Seeing many occurrences of if experiments.STRICT_OPTIONAL:
return UninhabitedType()
else:
return NoneTyp() made me think about the following reorganization. Instead of
Ideally the only difference between the two modes would be whether I haven't yet read the whole diff carefully, so I'm not sure how this will work out in practice. |
Something we might want to think about before too long is whether it makes sense to check some parts of a program (e.g., some of its dependencies) with strict optional checking and others without, and how we might plan to support that. |
Honestly I would be happier if this was for a brief period only. But not sure how realistic that I. |
I agree with Guido: I think it would be best to target having this only for a brief period. @rwbarton: I've thought about your suggestion, and I think it can work, but I actually find the current way of thinking about things to be easier. Making sure |
I've updated the new isinstance checking to only be active with --strict-optional enabled so this PR doesn't have to block on #1568, which is a bit complicated. |
if not x: | ||
g(x) # E: Argument 1 to "g" has incompatible type "Optional[int]"; expected None | ||
else: | ||
f(x) |
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.
Reveal the type of x
here to make sure it's not Any
or similar.
It would be nice to have test cases for various things that have been fixed. |
def assign_type(self, expr: Node, type: Type, | ||
def assign_type(self, expr: Node, | ||
type: Type, | ||
declared_type: Type, | ||
restrict_any: bool = False) -> None: |
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 docstring (at least explain declared_type
and what it's used for).
Docstring of
|
I'd like a more detailed docstring for
Also, add unit tests to |
One additional thing to test: see if overloaded functions work with |
@@ -1415,13 +1415,16 @@ main: note: In member "f" of class "A": | |||
[case testAttributePartiallyInitializedToNoneWithMissingAnnotation] | |||
class A: | |||
def f(self) -> None: | |||
self.x = None # E: Need type annotation for variable | |||
self.x = None |
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 should still produce Need type annotation for variable
. Reasoning:
- Strict optional checking really shouldn't have an effect on this. This change seems unrelated to the rest of the PR.
- There's not enough information in the method scope to infer a non-
None
type for the attribute. I wouldn't like to infer the type of an attribute from two method bodies (type inference should be local for predictability).
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, it would be useful to have a similar test case with strict optional enabled.
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.
The Need type annotation for variable
error didn't go away, it just needed to be moved to the [out] section. The only thing that changed is that we can now infer that x
is int
for the body of g(), so there's an additional int not callable error
.
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.
With strict optional checking enabled, None
will be inferred as a type if no other information is available other than the None assignment (and this is already tested).
This is good to rebase and land after resolving the 'Need type annotation for variable' issue. |
First pass strict Optional checking. Adds the experimental `--strict-optional` flag. Fixes #1450. With --strict-optional: - "None" in type annotations refers to NoneTyp, except as a return value, where it still refers to Void - None and List[None] will now be inferred as a type if no other information is available. - Class variables may be initialized to None without having an Optional type. Mypy does not currently check that they're assigned to in __init__ or elsewhere before use. See #1450 for more details. This also fixes the bug where mypy didn't understand that x.y = "foo" implied that x.y would be a str for the remaineder of that block. This isn't entirely sound, but is in line with the way we do isinstance checks.
Implementation of #1450. Largely complete -- just missing the ability for non-Optional class vars to be initialized to None.
Just ignore all the crazy commits -- they're still useful history for me, so I'd prefer not to squash yet.