-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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] Add support for string annotations #14151
Conversation
666e12e
to
5872fa4
Compare
|
f1fb77c
to
f3ee666
Compare
deferred_state: DeferredState, | ||
|
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.
TODO: add some documentation
DefinitionKind::AnnotatedAssignment(_annotated_assignment) => { | ||
// TODO self.infer_annotated_assignment_deferred(annotated_assignment.node()); | ||
} |
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.
TODO: add a comment on why annotated assignment are not deferred
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'm curious to read this comment!
Could we add some tests that annotations on annotated assignments in stub files and when from __future__ import annotations
is active, are in fact deferred? (That is, can contain forward references.)
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.
Added the test cases in assignment/annotations.md
if self.are_all_types_deferred() { | ||
for base in class.bases() { | ||
self.infer_expression(base); | ||
} | ||
for base in class.bases() { | ||
self.infer_expression(base); |
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.
The diff is to remove the self.are_all_types_deferred()
check because we're already in a deferred query here.
Although there are a couple of TODOs that I want to address mostly around documentation, I'd appreciate some initial feedback for any obvious issues that I've missed. |
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.
Didn't complete review yet, but I have to run, so submitting the few comments I have so far in case I don't get back to it tonight.
@@ -387,7 +391,7 @@ impl<'db> TypeInferenceBuilder<'db> { | |||
|
|||
/// Are we currently inferring deferred types? | |||
fn is_deferred(&self) -> bool { | |||
matches!(self.region, InferenceRegion::Deferred(_)) | |||
matches!(self.region, InferenceRegion::Deferred(_)) || self.deferred_state.is_deferred() |
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 sure if I'm missing an issue here, but maybe inferring an InferenceRegion::Deferred
should set self.deferred_state
and then this check can be simplified?
You have a note above to add docs for deferred_state
; I think how these two things relate (and why InferenceRegion::Deferred
overrides self.deferred_state
) is a good candidate for some clear docs.
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.
Ok after reading the whole diff more carefully, I think I understand this now, but yeah it would be great to document it.
@@ -1,9 +1,191 @@ | |||
# String annotations | |||
|
|||
## Simple |
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.
These tests are fabulous! Thorough, clear, and correct, the testing trifecta :)
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 excellent, great work! It turned out way less complicated than I thought it would be. I love that with a few small changes we can sidestep the whole issue of AST IDs and just store the resulting type on the string node. I think the result of this is that we won't be able to have hover types for individual parts of a string annotation, just the whole annotation, but personally I think that's fine; it is in fact just one expression, after all.
DefinitionKind::AnnotatedAssignment(_annotated_assignment) => { | ||
// TODO self.infer_annotated_assignment_deferred(annotated_assignment.node()); | ||
} |
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'm curious to read this comment!
Could we add some tests that annotations on annotated assignments in stub files and when from __future__ import annotations
is active, are in fact deferred? (That is, can contain forward references.)
@@ -387,7 +391,7 @@ impl<'db> TypeInferenceBuilder<'db> { | |||
|
|||
/// Are we currently inferring deferred types? | |||
fn is_deferred(&self) -> bool { | |||
matches!(self.region, InferenceRegion::Deferred(_)) | |||
matches!(self.region, InferenceRegion::Deferred(_)) || self.deferred_state.is_deferred() |
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.
Ok after reading the whole diff more carefully, I think I understand this now, but yeah it would be great to document it.
Do we know how we would extend this design to support resolving types of inner expressions? Do we know if pyright supports hovering for stringified type annotations? Not supporting expression-level-types might be problematic long term because it doesn't just affect hovering:
|
Yes, Pyright does support hover, goto definitions, references, etc. in string annotations. Looking a bit deeper into Pyright, I think the reason they're able to do this is because they parse string annotations during the parsing stage and store it on the AST. But, the thing that I missed, and is not in the design document, is that the Pyright parser also takes into account
I and Micha talked about this in our 1:1 today and I think it's fine to move forward with this implementation today and visit it at a later stage. We might have to spend some additional time in figuring out the LSP part though. It might also be useful to understand the user impact of this feature if and when this needs to be implemented to validate the time investment. Some of the ideas for the implementation would be (a) updating the parser / AST to accommodate the change (b) semantic index that's specific to string annotation and an additional layer that connects the semantic index from the file with these specific ones. |
Yes, I think (b) describes how I'd envisioned we could tackle this, if/when we need to. I think it might also be possible to do something even simpler that doesn't do a full semantic index of the AST from the stringified annotation, but just adds a new mechanism for attaching a type directly to a text range instead of a node? |
This separates the inference of string annotations in the deferred region. But, this creates complications in annotations that are only partially stringified e.g., `tuple[int, "Foo"]` where "Foo" is a forward reference. This commit exists so as to create a checkpoint in case some of the ideas explored here are useful.
This is the second attempt for string annotation which infers the string annotation types in the same definition query. This has the added advantage of avoiding to go through two salsa queries. It does this by maintaining a state on the builder and utilizes that to make certain decisions throughout the inference process.
39d0884
to
2d26568
Compare
2d26568
to
d07579c
Compare
Summary
This PR adds support for parsing and inferring types within string annotations.
Implementation (attempt 1)
This is preserved in 6217f48.
The implementation here would separate the inference of string annotations in the deferred query. This requires the following:
binding_ty
anddeclaration_ty
.definition_expression_ty
to determine the function return type and class bases.This has the following limitations:
Implementation (attempt 2)
This is the final diff in this PR.
The implementation here does the complete inference of string annotation in the same definition query by maintaining certain state while trying to infer different parts of an expression and take decisions accordingly. These are:
x: "Foo"
, if the "Foo" symbol is not defined then it won't exists in the symbol table even though it's being used. This is an invariant which is being allowed only for symbols in a string annotation.Design document: https://www.notion.so/astral-sh/String-Annotations-12148797e1ca801197a9f146641e5b71?pvs=4
Closes: #13796
Test Plan
red_knot
on LibCST (contains a lot of string annotations, specifically https://github.com/Instagram/LibCST/blob/main/libcst/matchers/_matcher_base.py), FastAPI (good amount of annotated code includingtyping.Literal
) and compare against themain
branch output