Skip to content

Typeddict context returned correctly when unambiguous #8212

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 8 commits into from
Jan 14, 2020

Conversation

PattenR
Copy link
Contributor

@PattenR PattenR commented Dec 28, 2019

Fixes #8156

This is a suggestion as to how we could handle getting the Typeddict context from a Union with more than one Typeddict.

The idea is that we check each of the Typeddict types to find one who's keys match that of the expression, if more than one match it's then ambiguous. The slightly weird thing is that this matching is only applied in the Union case, if there is only a single Typeddict it's returned as before.

I'm not sure that I've put the tests in the most appropriate place, and suggestions would be great.

Copy link
Collaborator

@msullivan msullivan left a comment

Choose a reason for hiding this comment

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

This looks good and should work decently I think. A few nits.

mypy/messages.py Outdated
self,
types: List[TypedDictType],
context: Context) -> None:
self.fail('Type of TypedDict is Ambiguous, could be any of {}'.format(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't capitalize Ambiguous

mypy/messages.py Outdated
types: List[TypedDictType],
context: Context) -> None:
self.fail('Type of TypedDict is Ambiguous, could be any of {}'.format(
([(format_type(typ), format_item_name_list(typ.items.keys())) for typ in types])
Copy link
Collaborator

Choose a reason for hiding this comment

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

The type error message here is ugly. My inclination is to just print the types, though if you can make the rest look good and having clear meaning then that's OK. We definitely don't want to be printing out a bunch of brackets and parens and quotes that come from python's default reprs.

If multiple names are being formatted, also, we want to use format_type_distinctly to make sure they don't end up getting given the same message. Unfortunately format_type_distinctly only works on two strings, so I went and made a PR (#8270) to fix that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I wasn't quite sure what to do about printing the types here. Thanks a lot for that PR, I've updated to use format_type_distinctly and it's much cleaner! Also if you can think of any slightly nicer wording do let me know.

@@ -3294,19 +3313,25 @@ def visit_dict_expr(self, e: DictExpr) -> Type:
assert rv is not None
return rv

def find_typeddict_context(self, context: Optional[Type]) -> Optional[TypedDictType]:
def find_typeddict_context(self, context: Optional[Type],
dict_expr: DictExpr) -> Optional[TypedDictType]:
Copy link
Collaborator

Choose a reason for hiding this comment

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

(Has the linter broken??). Indent dict_expr another space

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah this was weird, my vscode showed this being inline! I've added the space in but yeah weird this wasn't picked up.

@PattenR
Copy link
Contributor Author

PattenR commented Jan 11, 2020

Thanks for the review @msullivan! Any more comments would be great

Copy link
Collaborator

@JukkaL JukkaL left a comment

Choose a reason for hiding this comment

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

Thanks for the updates! Looks good, just one remaining nit.

A = TypedDict('A', {'@type': Literal['a-type'], 'a': str})
B = TypedDict('B', {'@type': Literal['a-type'], 'a': str})

c: Union[A, B] = {'@type': 'a-type', 'a': 'Test'} # E: Type of TypedDict is ambiguous, could be any of ('"A"', '"B"') \
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: Both single and double quotes in the error message ('"A"' etc.) look strange. It would be better to only use double quotes.

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 for the review! I've updated to have double quotes although I could also have used format_type_distinctly directly with the bare argument set to true, would have had the same result except for single quotes (maybe that would have been cleaner?).

Copy link
Collaborator

@JukkaL JukkaL left a comment

Choose a reason for hiding this comment

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

Thanks for the updates! Looks solid now.

@JukkaL JukkaL merged commit 974a58d into python:master Jan 14, 2020
sthagen added a commit to sthagen/python-mypy that referenced this pull request Jan 15, 2020
Typeddict context returned correctly when unambiguous (python#8212)
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.

Dictionaries not accepted for implicit cast to TypedDict Union
4 participants