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

Self Type #2193

Merged
merged 56 commits into from
Oct 27, 2016
Merged

Self Type #2193

merged 56 commits into from
Oct 27, 2016

Conversation

elazarg
Copy link
Contributor

@elazarg elazarg commented Sep 29, 2016

Implement #1212. I don't know if that's The Right Way To Do It.

There is no attempt to check or disallow abuse of this feature.

@elazarg
Copy link
Contributor Author

elazarg commented Sep 29, 2016

@JukkaL It would be nice if you'll skim to see that nothing inherently wrong is going on here.

@JukkaL
Copy link
Collaborator

JukkaL commented Sep 29, 2016

I did a very quick pass and the feedback I have is that this needs more time to study than I have right now :-)

I'll take a more careful look tomorrow.

@gvanrossum gvanrossum changed the title Self Type (unfinished) [WIP] Self Type Sep 29, 2016
INVARIANT = 0 # type: int
COVARIANT = 1 # type: int
CONTRAVARIANT = 2 # type: int
# TODO: SELF_VARIANCE = 2 # type: int
Copy link
Contributor Author

@elazarg elazarg Sep 30, 2016

Choose a reason for hiding this comment

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

What do you think? I am not sure what this is, For now simple INVARIANT seems to suffice

Copy link
Collaborator

Choose a reason for hiding this comment

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

INVARIANT should be fine. I don't see a need for special variance.

)


class TypeVarInstantiator(TypeTranslator):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it implemented somewhere else already?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Look at mypy/expandtype.py.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I use it I get partial types. I must be missing something about how the whole thing works.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The partial types appear only when I use reveal_type, and it seems to be orthogonal to self-type specifically (it happens with normal generic functions too).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My mistake - it was the erased flag. I turned it off for this "inference".

@gvanrossum
Copy link
Member

I'm trying to kick the tires here and I don't understand why this example doesn't work:

from typing import TypeVar

T = TypeVar('T', bound='C')

class C:
    def copy(self: T) -> T:
        return self

def clone(arg: T) -> T:
    return arg.copy()

The copy() method type-checks wonderfully, but on the very last line I get:

__tmp__.py: note: In function "clone":
__tmp__.py:10: error: Incompatible return value type (got "C", expected "T")

@JukkaL
Copy link
Collaborator

JukkaL commented Oct 3, 2016

Sorry, I've been busy with other stuff and haven't been able to review this. Tomorrow should be better!

@gvanrossum
Copy link
Member

Similar:

from typing import Type, TypeVar

T = TypeVar('T', bound='C')

class C:
    @classmethod
    def new(cls: Type[T]) -> T:
        return cls()

def make(cls: Type[T]) -> T:
    return cls.new()

Gives:

__tmp__.py: note: In function "make":
__tmp__.py:11: error: Incompatible return value type (got "C", expected "T")

@@ -1648,9 +1647,10 @@ def accept(self, visitor: NodeVisitor[T]) -> T:
#
# If T is contravariant in Foo[T], Foo[object] is a subtype of
# Foo[int], but not vice versa.
CONTRAVARIANT = -1 # type: int
Copy link
Collaborator

Choose a reason for hiding this comment

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

The change of the constant value seems unrelated to the rest of diff and unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My poor git skills seems to have failed me. I did revert this one.

@@ -698,6 +705,31 @@ def deserialize(cls, data: JsonDict) -> 'CallableType':
is_classmethod_class=data['is_classmethod_class'],
)

def bind_self(self, actual_self: Type = None) -> 'CallableType':
Copy link
Collaborator

Choose a reason for hiding this comment

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

Our convention is to keep Type (and Node) subclasses as almost pure data. This is complex enough that it would be better moved to a module-level function. I suspect you can use a type variable ranging over FunctionLike and CallableType to give it a precise enough type.

class A:
def copy(self: T) -> T:
if B():
return A()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Try to use the # E: error message convention for the error messages for readability and consistency. If it doesn't work because of all the notes (it shouldn't be a problem here), use a # Error comment on lines that generate an error to make it easier to match errors to lines. You can leave the main: note: In ... in the [out] section and add # E: ... comments here.

@@ -1608,13 +1608,17 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type:
return AnyType()
if not self.chk.in_checked_function():
return AnyType()
selftype = self.chk.function_stack[-1].arguments[0].variable.type
return analyze_member_access(name=e.name, typ=fill_typevars(e.info), node=e,
# fill_typevars(e.info) erases type variables
Copy link
Collaborator

Choose a reason for hiding this comment

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

This comment isn't right. fill_typevars adds type variables, erasure means something like stripping away type variables or replacing them with the upper bound / Any, which isn't happening here. If I have a class define as class C(Generic[T]): then fill_typevars will create a type C[T] when called with the type info for C as an argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So I did get it wrong there. Yet if I pass fill_typevars() as actual_type I get the erasure on access.

I'm completely confused now :\

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh. wait. it's only confusing because we talk about different variables... sorry. I'll update.

args = self.chk.function_stack[-1].arguments
# An empty args with super() is an error, but we need something in declared_self
declared_self = args[0].variable.type if args else erased_self
return analyze_member_access(name=e.name, typ=erased_self, node=e,
Copy link
Collaborator

Choose a reason for hiding this comment

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

All the checker tests pass if I pass declared_self as the typ argument. Can we just get rid of erased_self here, as I don't see any use for it? We can return Any without calling analyze_member_access in case there are no arguments (worth generating an error message, though). This would help resolve the actual_self documentation issue in analyze_member_access as well, I believe, as we can rename the argument to original_type and it doesn't need to be special cased for this particular call site. What do you think?

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 am not sure this will not break for generic classes. But perhaps we can wait and see.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, it could well break for generic classes, but as we don't have tests for them we don't know whether they work right now. Let's do that separately. Even for generic classes this is perhaps not the best approach.


This is a general operation that supports various different variations:

1. lvalue or non-lvalue access (i.e. setter or getter access)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please retain This is general ... and the following numbered list as they are still relevant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops. Sorry.

@JukkaL
Copy link
Collaborator

JukkaL commented Oct 27, 2016

Okay, this is almost ready to merge! Thanks for your patience -- this is one of trickiest type system features that I've reviewed recently. It's also going to be pretty useful, I feel.

If you address the comment about the docstring and the test case(https://github.com/python/mypy/pull/2193/files#r85375637) I can merge this. Then one of us can create tasks for follow-up work.

@elazarg
Copy link
Contributor Author

elazarg commented Oct 27, 2016

Thank you all - it's been a pleasure :)

@JukkaL
Copy link
Collaborator

JukkaL commented Oct 27, 2016

@ilevkivskyi Sorry, I think that I didn't answer your question. I'm still undecided on this issue.

Let's continue the discussion in separate issues that I just created (also feel free to create additional ones for things that I missed): #2351, #2352, #2353 and #2354.

@elazarg elazarg deleted the self_type branch October 28, 2016 00:11
gvanrossum pushed a commit that referenced this pull request Oct 29, 2016
@elazarg elazarg mentioned this pull request Nov 11, 2016
gvanrossum pushed a commit that referenced this pull request Nov 13, 2016
(Reopening #2408 after the revert #2414. This fixes parts of #2090)
This PR does three things:

    Fix the handling of TupleType (pass original_type recursively)
    Fix the handling of TypeType (avoid ad-hoc buggy special casing)
    Make NamedTuple's _replace() and _make() return selftype, serving as test case for (1) and (2)

As a consequence of (1), some error messages are changed, as exemplified in check-isinstance.test. I think it's better now, but if it isn't, perhaps we should separate original_type and report_type as discussed in #2193.
@jboning jboning mentioned this pull request Dec 8, 2016
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