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

[Reverted] Fixes to union simplification, isinstance and more #3025

Merged
merged 19 commits into from
Mar 29, 2017

Conversation

JukkaL
Copy link
Collaborator

@JukkaL JukkaL commented Mar 19, 2017

The main change is that unions containing Any are no longer simplified
to just Any. Use proper subtype check for union simplification and
implement proper subtype checks more fully.

This required changes in various other places to keep the existing
semantics and to avoid regressions and inconsistencies.

Fixes #1914.
Fixes #2978.

The main change is that unions containing Any are no longer simplified
to just Any.

This required changes in various other places to keep the existing
semantics, and resulted in some fixes to existing test cases.
We generally don't have fully constructed TypeInfos so
we can't do proper union simplification. Just implement
simple-minded simplifaction that deals with the cases we
care about.
mypy/subtypes.py Outdated
return UnionType.make_union(new_items)
else:
return t


def is_proper_subtype(t: Type, s: Type) -> bool:
def is_proper_subtype(left: Type, right: Type) -> bool:
Copy link
Member

Choose a reason for hiding this comment

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

t and s should also be replaced with left and right in the docstring below.

reveal_type(u(object(), t_a)) # E: Revealed type is 'builtins.object*'

# Union between type objects
reveal_type(u(t_o, t_a)) # E: Revealed type is 'Union[Type[Any], Type[builtins.object]]'
Copy link
Member

Choose a reason for hiding this comment

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

Union[Type[Any], Type[object]] and Union[type, Type[object]] should give the same result, since type is equivalent to Type[Any] (at least it is what PEP 484 says). Maybe this could be fixed by adding a special case in spirit of #3012 at the end of visit_type_type.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is not feasible since Type[Any] and type have different internal representations. We can't have two different types being proper subtypes of each other unless they are the same type -- otherwise the subtyping relation doesn't form a lattice.

reveal_type(u(C(), None)) # E: Revealed type is 'Union[builtins.None, __main__.C*]'
reveal_type(u(None, C())) # E: Revealed type is 'Union[__main__.C*, builtins.None]'
reveal_type(u(a, None)) # E: Revealed type is 'Union[builtins.None, Any]'
reveal_type(u(None, a)) # E: Revealed type is 'Union[Any, builtins.None]'
Copy link
Member

Choose a reason for hiding this comment

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

These two lines are duplicated here and in the test case above.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, wait, this test case almost entirely duplicates previous one. Is this intentional? (I suppose both cases are run with --strict-optional)

mypy/subtypes.py Outdated
return False

def visit_typeddict_type(self, left: TypedDictType) -> bool:
# TODO: Does it make sense to support TypedDict here?
Copy link
Member

Choose a reason for hiding this comment

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

If this is supposed to be used only with isinstance and issubclass, then no, since they fail at runtime for TypedDicts.

Copy link
Member

Choose a reason for hiding this comment

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

They're also used for union simplification so we still need to do this.

mypy/subtypes.py Outdated
if isinstance(right, TypeType):
return is_proper_subtype(left.item, right.item)
if isinstance(right, CallableType):
# This is unsound, we don't check the __init__ signature.
Copy link
Member

Choose a reason for hiding this comment

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

(And so is the previous case (TypeType).)

mypy/subtypes.py Outdated
# This is unsound, we don't check the __init__ signature.
return right.is_type_obj() and is_proper_subtype(left.item, right.ret_type)
if isinstance(right, Instance):
if right.type.fullname() in ('builtins.type', 'builtins.object'):
Copy link
Member

Choose a reason for hiding this comment

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

I've got a feeling this means that issubclass(Type[int], type) and issubclass(Type[int], Type) will not be treated the same. (Those expressions aren't valid literally, but I think a similar situation can arise in other ways, or perhaps using isinstance(x, type) vs. isinstance(x, Type).)

mypy/typeanal.py Outdated
return t
if isinstance(t, NoneTyp):
return t
if isinstance(t, UnionType) and any(isinstance(item, NoneTyp)
Copy link
Member

Choose a reason for hiding this comment

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

Do we always flatten unions? Otherwise this could be fooled by Union[X, Union[Y, None]]. (Not that I'm sure it matters.)

mypy/subtypes.py Outdated
return True

def visit_erased_type(self, left: ErasedType) -> bool:
return True
Copy link
Member

Choose a reason for hiding this comment

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

Maybe assert False instead?

mypy/subtypes.py Outdated
self.right = right

def visit_unbound_type(self, left: UnboundType) -> bool:
return True
Copy link
Member

Choose a reason for hiding this comment

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

Maybe assert False instead?

reveal_type(x)
[out]
tmp/b.py:6: error: Revealed type is 'a.X'
tmp/b.py:8: error: Revealed type is 'Tuple[Any, fallback=a.X]'
Copy link
Member

Choose a reason for hiding this comment

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

Might be worth adding another test that just uses N instead of X everywhere.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This doesn't work because of #3054.


[case testOptionalAndAnyBaseClass]
from typing import Any, Optional
class C(Any):
Copy link
Member

Choose a reason for hiding this comment

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

Should use a value of type Any as base class, since just inheriting from Any could be statically rejected.


[case testUnionSimplificationWithStrictOptional]
from typing import Any, TypeVar, Union
class C(Any): pass
Copy link
Member

Choose a reason for hiding this comment

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

Ditto.

x = None # type: Optional[C]
x.foo() # E: Some element of union has no attribute "foo"

[case testUnionSimplificationWithStrictOptional]
Copy link
Member

Choose a reason for hiding this comment

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

Test name should be more like "certain unions are not simplified"

[case testIsinstanceAndOptionalAndAnyBase]
from typing import Any, Optional

class A(Any): pass
Copy link
Member

Choose a reason for hiding this comment

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

Also use a base of type Any.

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

I'm afraid you're going to be mostly on your own here, I don't really understand how all this code hangs together. :-(

@@ -911,7 +911,8 @@ def foo() -> Union[int, str, A]: pass

def bar() -> None:
x = foo()
x + 1 # E: Unsupported left operand type for + (some union)
x + 1 # E: Unsupported left operand type for + (some union) \
# E: Unsupported operand types for + (likely involving Union)
Copy link
Member

Choose a reason for hiding this comment

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

Really? Two errors, both vaguely mentioning "union" without indicating which union item(s) are involved?

Copy link
Collaborator Author

@JukkaL JukkaL Mar 29, 2017

Choose a reason for hiding this comment

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

Yeah, this is unfortunate, but this wasn't trivial to fix. I'll create a follow-up issue to address the duplicate messages (and maybe generally improve error messages related to union types.

if isinstance(x, type):
reveal_type(x) # E: Revealed type is 'Type[builtins.int]'
else:
reveal_type(x) # Unreachable
Copy link
Member

Choose a reason for hiding this comment

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

Which reminds me (OT), maybe be should have a debugging flag that warns when a block is deemed unreachable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That could be nice. There are some cases when this is valid, foe example when type checking AnyStr some blocks are often unreachable for either str or bytes, so the required logic may be somewhat involved.

def visit_instance(self, left: Instance) -> bool:
right = self.right
if isinstance(right, Instance):
for base in left.type.mro:
Copy link
Contributor

Choose a reason for hiding this comment

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

This breaks on the code snippet from #3069: when left refers to a NamedTuple, left.type.mro is None. I'm guessing it can be fixed by adding mro to instances created by NamedTuple?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If mro is None during type checking a lot of things might break. The right fix seems to be to populate mro always instead of papering over it here. Named tuple initialization is currently broken in some contexts (see #3054, for example).

@JukkaL JukkaL merged commit 67cda6e into master Mar 29, 2017
@gvanrossum gvanrossum deleted the proper-subtype branch March 29, 2017 18:28
@gvanrossum
Copy link
Member

Whee! Thanks.

JukkaL added a commit that referenced this pull request Mar 29, 2017
I found a major bug when experimenting with real-world codebases.

This reverts commit 67cda6e.
gvanrossum pushed a commit that referenced this pull request Mar 29, 2017
…3080)

I found a major bug when experimenting with real-world codebases.

This reverts commit 67cda6e.
@gvanrossum gvanrossum changed the title Fixes to union simplification, isinstance and more [Reverted] Fixes to union simplification, isinstance and more Mar 30, 2017
JukkaL added a commit that referenced this pull request Mar 30, 2017
The main change is that unions containing Any are no longer simplified
to just Any. Also union simplification now has a deterministic result unlike
previously, when result could depend on the order of items in a union
(this is true modulo remaining bugs).

This required changes in various other places to keep the existing
semantics, and resulted in some fixes to existing test cases. I also
had to fix some tangentially related minor bugs that were triggered
by the other changes.

We generally don't have fully constructed TypeInfos so
we can't do proper union simplification during semantic
analysis. Just implement simple-minded simplification that 
deals with the cases we care about.

Fixes #2978. Fixes #1914.
ilevkivskyi pushed a commit that referenced this pull request Apr 4, 2017
* Fixes to union simplification, isinstance and more (#3025)

The main change is that unions containing Any are no longer simplified
to just Any. Also union simplification now has a deterministic result unlike
previously, when result could depend on the order of items in a union
(this is true modulo remaining bugs).

This required changes in various other places to keep the existing
semantics, and resulted in some fixes to existing test cases. I also
had to fix some tangentially related minor bugs that were triggered
by the other changes.

We generally don't have fully constructed TypeInfos so
we can't do proper union simplification during semantic
analysis. Just implement simple-minded simplification that 
deals with the cases we care about.

Fixes #2978. Fixes #1914.

* Fix strict optional and partial type special case and fix test

* Fix lint

* Make is_subtype and is_proper_subtype do promotion the same way

Fixes #1850.

* Drop reference to ErrorType (recently expunged)

* Fix merge mistake

* Fix another merge mistake

* Update based on review

* Add test case

* Fix redundant TODO due to copy paste
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.

4 participants