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

Fix crash involving explicit any flag and Required #12039

Merged

Conversation

hmc-cs-mdrissi
Copy link
Contributor

@hmc-cs-mdrissi hmc-cs-mdrissi commented Jan 22, 2022

Description

Fixes #12038. This adds an accept method to Required that delegates to the inner type. @davidfstr

Test Plan

I ran the tests locally and they passed. I added two small test cases to check-typeddict.test that correspond to fixed crash.

Copy link
Contributor

@A5rocks A5rocks left a comment

Choose a reason for hiding this comment

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

I can't approve the workflow runs cause I'm not a mypy maintainer. (By the way, don't take these comments too seriously, I don't really know too much about mypy internals)

@@ -43,7 +43,7 @@ class Iterator(Iterable[T_co], Protocol):
def __next__(self) -> T_co: pass

class Sequence(Iterable[T_co]):
def __getitem__(self, n: Any) -> T_co: pass
def __getitem__(self, n: Any) -> T_co: pass # type: ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this # type ignored? Perhaps leave a note for future viewers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It has an Any and my new test case checks for explicit Any with typeddict stubs. Alternatively I could add silent import flag to the test case.

Copy link
Contributor

Choose a reason for hiding this comment

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

What you now makes sense then, I suppose putting a specific code on the type: ignore (like, if you run with --show-error-codes it will tell you what code to put) would be appreciated. But I don't know if that's the style tests normally take so...

Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose putting a specific code on the type: ignore [...] would be appreciated

+1

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've added the error code. The code is vague [misc] so I included a comment too on which error it was.

@@ -360,6 +360,9 @@ def __repr__(self) -> str:
else:
return "NotRequired[{}]".format(self.item)

def accept(self, visitor: 'TypeVisitor[T]') -> T:
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels icky, but I presume it's needed?

I think I would have preferred fixing this one layer up -- whatever is trying to figure out if this is a Required should maybe know (this only works if that one layer up is related to TypedDicts which it probably isn't).

Copy link
Contributor Author

@hmc-cs-mdrissi hmc-cs-mdrissi Jan 22, 2022

Choose a reason for hiding this comment

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

The caller is this function, https://github.com/python/mypy/blob/master/mypy/typeanal.py#L1304. It doesn't feel like that function should need to have knowledge of typeddicts/required. This function only wants to check does an Annotation include Any somewhere in it.

The caller above that is https://github.com/python/mypy/blob/master/mypy/semanal_typeddict.py#L305. This does look like a reasonable place. If you would prefer I have the typeddict semantic analyzer strip Required/NotRequired before calling explicit any check I can do that.

Also I appreciate the comments. This is my first pr/time looking at mypy's actual code. I've been using it for a while, but have never had a bug that looked small enough I could just do and this made me explore mypy code.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think either approach works:

  • Your current approach is more broad and will generally fix the problem with accepting a Required anywhere, as long as just having the item accept it is what is always meant (it sounds like it might be).
  • Just changing the function above is less broad but also has less chance of accidentally introducing subtly wrong behavior.

I think the "subtly wrong behavior" is so unlikely that whatever approach is easier will work.

@davidfstr
Copy link
Contributor

Thanks for the PR @hmc-cs-mdrissi !

Looks like the underlying problem fixed here is that RequiredType (directly) extends Type but did not previously implement the abstract method Type.accept. @hmc-cs-mdrissi I think defining the accept method to directly enter the enclosed item, as you have done, is the right call here. 👍

@davidfstr
Copy link
Contributor

I notice that TypeGuardedType also directly extends Type and does not implement Type.accept, so there's probably a similar crash issue with TypeGuardedType.

CC @erictraut - TypeGuard PEP author
CC @hauntsaninja - most recent contributor to make a major change to TypeGuardedType
CC @gvanrossum - original contributor for TypeGuardType

@davidfstr
Copy link
Contributor

davidfstr commented Jan 23, 2022

It would be nice if running mypy on itself would highlight that RequiredType (and TypeGuardedType) fail to implement abstract method Type.accept, rather than that error being discovered at runtime.

A cursory inspection of the mypy documentation suggests that mypy understands an "abstract method" in the context of explicitly-defined Abstract Base Classes. So it might be possible to mark Type.accept with @abc.abstractmethod to get mypy to flag concrete classes like TypeGuardedType which fail to implement accept. However I'm not familar with ABCs so I don't know if that would actually work.

@gvanrossum
Copy link
Member

Sadly I no longer remember enough of mypy internals to be useful here. There are several new maintainers though!

@hmc-cs-mdrissi
Copy link
Contributor Author

hmc-cs-mdrissi commented Jan 24, 2022

Typeguard doesn't crash, but that's because it doesn't even check for explicit Any. This code,

def foo(x: object) -> TypeGuard[Any]:
  ...

when run with mypy --disallow-any-explicit produces no type errors. So looks like typeguard probably needs an accept and also to check for any.

As for abstract method issue, that works. If I add abstractmethod + ABC to Type class it produces a type error for TypeGuardType.

I'll work on a small separate pr for abstract class/method for accept.

edit: The typeguard issue is less clear to me. Some debugging the reason typeguard explicit any checks does nothing is return type is normalized to bool before explicit Any check happens. I'll make a separate bug ticket for it and may take another look later.

@davidfstr
Copy link
Contributor

LGTM, with the new changes.

I cannot merge this PR myself. @JukkaL ?

@JelleZijlstra JelleZijlstra merged commit d5d077b into python:master Jan 26, 2022
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.

Explicit Any Crashes With Required for Functional TypedDict
5 participants