-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Last strict optional fixes #4070
Last strict optional fixes #4070
Conversation
…nal-fixes Conflicts: mypy/checker.py mypy/checkexpr.py
How hard would it be to separate the new feature from the rest of the PR?
(And describe it in an issue while you're at it?)
|
mypy/checker.py
Outdated
# This is only OK for built-in containers, where we know the behavior of __contains__. | ||
if isinstance(tp, Instance): | ||
if tp.type.fullname() in ['builtins.list', 'builtins.tuple', 'builtins.dict', | ||
'builtins.set', 'builtins.frozenfet']: |
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.
frozenset, not frozenfet
mypy/semanal.py
Outdated
[defn.type.ret_type])) | ||
[defn.type.ret_type]) or | ||
# We are running tests | ||
self.named_type('typing_full.Awaitable', [defn.type.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.
This should be indented more (and maybe rearranged; I think it's too much for one statement).
OK, I opened #4071 and separated the feature plus added tests in #4072. I would like to keep the code here, so that we can avoid: if fullname in ('enum.Enum', 'enum.IntEnum'):
assert fullname is not None etc. The PRs can be merged in arbitrary order, however it makes sense to merge #4072 first. |
…nal-fixes Conflicts: mypy/semanal.py mypy_self_check.ini
@JukkaL While this is fresh could you please review also this PR (to avoid more conflicts)? |
Sure, I'll give it a go. |
Dang, there's already another conflict. :-( |
Should be fixed now. |
Thanks, I'm going to leave the review to Jukka (who's busy moving this
weekend).
|
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, looks good! This migration has been a big undertaking and it's great to see the final strict optional exceptions removed. I left just some minor comments.
mypy/semanal.py
Outdated
@@ -393,6 +396,7 @@ def visit_func_def(self, defn: FuncDef) -> None: | |||
# Top-level function | |||
if not defn.is_decorated and not defn.is_overload: | |||
symbol = self.globals.get(defn.name()) | |||
assert symbol, "Global function not found in globals" |
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.
We could instead just use self.globals[defn.name()]
above?
ret_type = self.named_type_or_none('typing.Awaitable', [defn.type.ret_type]) | ||
if ret_type is None: | ||
# We are running tests. | ||
ret_type = self.named_type('typing_full.Awaitable', [defn.type.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.
What is typing_full
? Could we just blow up if typing.Awaitable
is not available?
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.
Without this line many tests fail (see the comment just above). I think this is because those tests use fixtures/typing-full.py
. A better fix would be to always name it typing
in tests, but I think this should be a separate PR.
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.
So the issue is that typing-full.py
is imported as module named typing_full
in tests? If this is true, this should be fixed (in another PR).
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.
Hm, my initial analysis is wrong, the problem is how named_type
works. I will make a PR now.
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, opened #4158
mypy/semanal.py
Outdated
self.errors.pop_function() | ||
|
||
def prepare_method_signature(self, func: FuncDef) -> None: | ||
"""Check basic signature validity and tweak annotation of self/cls argument.""" | ||
# Only non-static methods are special. | ||
assert self.type is not None, "This method should be only called inside a class" |
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.
You could remove this assert by adding a TypeInfo
argument and passing it from the caller.
@@ -534,7 +541,7 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: | |||
assert defn.impl is defn.items[-1] | |||
defn.items = defn.items[:-1] | |||
elif not self.is_stub_file and not non_overload_indexes: | |||
if not (self.is_class_scope() and self.type.is_protocol): | |||
if not (self.type and not self.is_func_scope() and self.type.is_protocol): |
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.
Random observation: Here it would be useful is is_class_scope()
could return the binding self.type is not None
when it returns true (there is a feature proposal about this, I think).
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.
Yes, I also noticed it would be very useful to somehow "remember" the bindings. For simple cases I was thinking about something like inlining methods (very approximately):
@inline
def meth(self) -> bool:
return self.x is not None and self.y is not None
if self.meth(): # use original expression returned by 'meth' here
...
@@ -690,8 +696,10 @@ def analyze_class_body(self, defn: ClassDef) -> Iterator[bool]: | |||
if prohibited in named_tuple_info.names: | |||
if nt_names.get(prohibited) is named_tuple_info.names[prohibited]: | |||
continue | |||
ctx = named_tuple_info.names[prohibited].node | |||
assert ctx is not 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.
What if the definition is a bound type variable where node
is None
, I think? In a separate PR, it would be nice to make the node
attribute non-optional (this is something we've discussed before).
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.
What if the definition is a bound type variable where
node
isNone
, I think?
Currently generic named tuples are not supported, so I thought this should never happen.
I would keep this assert and then if it ever fails it will be clear what happens there.
if isinstance(defn.metaclass, NameExpr): | ||
metaclass_name = defn.metaclass.name | ||
elif isinstance(defn.metaclass, MemberExpr): | ||
metaclass_name = get_member_expr_fullname(defn.metaclass) | ||
else: | ||
if metaclass_name is 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.
Ah does this fix a bug, in case get_member_expr_fullname
returns None
? If yes, can you add a test case for the original issue?
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.
Indeed, this fixes a crash (although it was not my intention, I just fixed the optional type here). I will add a test.
@@ -2576,6 +2595,7 @@ def parse_typeddict_args(self, call: CallExpr, | |||
for t in types: | |||
if has_any_from_unimported_type(t): | |||
self.msg.unimported_type_becomes_any("Type of a TypedDict key", t, dictexpr) | |||
assert total is not 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 seems redundant, since we return if total
is None
above.
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.
Not from mypy's point of view. We return if total
is None
and len(args) == 3
. I think this could be fixed by #2008
@JukkaL Thanks for review! I pushed fixes in a new commit. |
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.
Looks good now! Thanks for finishing the strict-optional migration!
This makes mypy completely --strict-optional clean. Fixes #1955.
In #4070, `CallableType.__init__` was changed to accept `arg_names: Sequence[str | None]` so we could pass e.g. `list[str]` to it. We're making a similar change to `CallableType.copy_modified`.
Fixes #1955
This PR will make mypy completely
--strict-optional
clean. In addition to signature fixes and other minor fixes (mostly insemanal.py
) I introduce a change in mypy as a typechecker, I have noticed a pattern that appears several times, but was not understood by mypy:I allow this only for built-in containers such as
list
,dict
,set
,frozenset
, andtuple
, since other types can change the semantics of__contains__
. (Also this way it is a small change, a general treatment ofContainer
subclasses will require code churn to getnamed_type
).UPDATE: The feature part now has a separate PR #4072.