Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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
[red-knot] Initial tests for instance attributes #15474
[red-knot] Initial tests for instance attributes #15474
Changes from 15 commits
a7cd7e2
72bc9dd
32ada58
d7845e2
3331d30
cc56a96
106d613
fbf4a1b
a7d211e
a3692f7
0ba2ed0
5a1bcb7
4c82e04
1547914
2395aa9
733c1a7
87305b1
b2c1dc9
File filter
Filter by extension
Conversations
Jump to
There are no files selected for viewing
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 is an interesting question. Both pyright and mypy have heuristics to widen literal types when they think it's "probably more useful." This seems a bit unprincipled to me, but I'm open to the possibility that we'll also need to do it. I guess my question is, if the only assignment to
self.pure_instance_variable1
that occurs anywhere in the class isself.pure_instance_variable1 = "value set in __init__"
, then why wouldn't you want it typed asLiteral["value set in __init__"]
? Just to accommodate external code setting it to some other value? That seems like an edge case, and not too much to ask you to add an explicit: str
if that's what you wanted.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.
Subclasses also won't be able to assign a type to the attribute unless the type is assignable to the
Literal[...]
typeHmm, I don't agree. I think it's quite common for code to only assign an instance variable in the
__init__
method of the class, but with the intention that it should be possible to reassign the attribute from other scopes.Surely that violates the gradual guarantee? And this doesn't seem to tackle at all the problem of typed code interacting with untyped third-party code, which may be unwilling to add type annotations in a timely manner?
I'm leaning towards either
Unknown | Literal[...]
orUnknown | str
for cases like these. Haven't made my mind up about which is preferable there (haven't thought too deeply about it yet!)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, but so does widening the literal type to
str
. That's what I mean by "unprincipled" -- it doesn't fundamentally change anything, just arbitrarily chooses some wider type because we think it's "probably what you meant."I like the idea of a union with
Unknown
for inferred attribute types! I think that is the principled gradual-guarantee approach here. It will be more forgiving than what people are used to, but I generally like it if we take the gradual guarantee more seriously than existing type checkers: if you want more strictness, annotate.(I don't see why we would special-case converting it to
Unknown | str
instead ofUnknown | Literal[...]
if we're unioning withUnknown
-- I think the union already takes care of the problematic aspects of the literal inference, by allowing you to assign anything.)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.
Depending on how we answer the above question, arguably this should be an error
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.
I think there is some question about the extent to which we'll want to do this narrowing. It's unsound in general (because we can't really say with any certainty what happens to
c_instance
between that assignment and this check), and particularly likely to be unsound in global scope. But we'll probably need to do some of it.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've had this conversation several times now 😆 I still favour the more pragmatic behaviour of mypy and pyright over the stricter behaviour of pyre here. But yeah, we can debate the details at a later stage
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 saying we shouldn't but this will require traversing the entire class body. This could be somewhat expensive (at least it's more expensive than only doing so for
__init__
).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.
Mypy and pyright both support this, so I think we have to, expensive though it may be. Users will expect their type checker to support this and will complain if it doesn't. It's also unfortunately reasonably common in Python to do things like
I think we probably don't need to support these, certainly not initially
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 can also experiment with varying degrees of laziness here. For first-party code that we're actually checking, I think we'll probably need to know all instance attributes of every class. But for third-party/stdlib code that our first-party code is interacting with, it often probably isn't necessary to know what instance attributes the class has and, even if it is, it might not be necessary to materialize the full set of instance attributes the class has. For something like the following, we could plausibly short-circuit if we find the
foo
attribute defined in__init__
or__new__
, and not bother analyzing the other methods theSomeClass
defines: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.
Probably the even more realistic example would have this method called from
__init__
, but if we do that in the test it raises more questions about whether we're trying to detect that call...