-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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] infer basic (name-based) annotation expressions #13130
[red-knot] infer basic (name-based) annotation expressions #13130
Conversation
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.
Nice! A couple of nitpicks below.
Here's some high-level thoughts that shouldn't block this PR, just things we could think about as followups:
- it might be nice if
infer_annotation_expression
took a customTypeExpression
enum as its input argument rather than theast::Expr
enum, to codify the fact that a valid type expression necessarily excludes certain Python expression nodes entirely, such asExpr::Call
s,Expr::Lambda
s,Expr::Named
, etc. - currently we're not looking at any qualifiers an annotation expression might or might not include that wrap the annotation's type expression. But in the long run, we'll want to take note of those and record them somewhere. It might make sense for
infer_annotation_expression
to return something like(HasSet<TypeQualifier>, Type<'db>)
rather than simplyType<'db>
.
Again, these high-level comments are just me thinking out loud about how we might want this code to work in the long run; not really anything you need to respond to in this PR!
Replying to a couple of the high-level thoughts here, though I agree that neither of them should be implemented in this PR:
I'm not sure about this. It implies that we would then need to have some other method that takes an It seems to me like this is an idea we can keep in mind if at some point things reach a level of complexity where it really concretely improves type safety (for instance, if we have built up a large collection of methods that are subsidiary to
Yes, agreed. Probably best to sort out the details here once we are adding support for a type qualifier, so we can design it in context with how the information is used. |
|
- Introduce methods for inferring annotation and type expressions. - Correctly infer explicit return types from functions where they are simple names that can be resolved in scope.
Co-authored-by: Alex Waygood <alex.waygood@gmail.com> Co-authored-by: Carl Meyer <carl@astral.sh>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
This does not yet *actually* fix the underlying issue we were hoping it would fix, i.e. a panic when inferring types in `typing.pyi` (as seen when testing against `tomllib`), but it should be the first step in the right direction to enabling that.
This suggests that we are going to end up with a lot of this code scattered around. Once we have enough examples, we may want to take a step back and work out how to refactor this to be less ad hoc.
We require that all nodes have a type, even if that type is `Unknown` or `Unbound`, so in the case of annotation and type expressions, we must recurse through all child expressions to identify their types, even if we know they are actually “nonsense”. This will also be helpful in the future when we have a language server, where we would want to show as much valid information as possible even in invalid contexts.
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.
Great work! In terms of the broad strokes, this looks excellent.
A lot of my comments are basically just a single comment that I don't think we should add such fine-grained tracing of type inference by default; if we did it everywhere, it'd be so noisy in any realistic case that you'd never be able to find anything. When debugging at that level, it works better IMO to manually add the specific dbg!()
calls that are relevant to the case you're debugging. Open to discussing this if you think I'm missing the value of this tracing!
Ha, very strongly agreed re: tracing instrumentation – I was going to ask if you thought any of those were valuable to keep, and I think the answer here is basically “no”. I’ll double check on the annotation-vs.-type-expressions thing as well and respond on that specific comment. |
Co-authored-by: Carl Meyer <carl@oddbird.net>
Co-authored-by: Carl Meyer <carl@astral.sh>
CodSpeed Performance ReportMerging #13130 will not alter performanceComparing Summary
|
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.
Looks great!
@@ -77,6 +77,7 @@ fn infer_definition_types_cycle_recovery<'db>( | |||
_cycle: &salsa::Cycle, | |||
input: Definition<'db>, | |||
) -> TypeInference<'db> { | |||
tracing::trace!("infer_definition_types_cycle_recovery"); |
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 should change this to an actual sentence rather than the method name. Tracing messages are user facing (not trace
but we might decide to make them user facing in the future).
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 function is a partially-broken temporary crutch just to get us through until we have fixpoint iteration and full deferred resolution of annotations in the necessary places; it should go away. So I'm not too worried about this particular trace message sticking around for a long time. But this is a good point.
@@ -2059,6 +2086,173 @@ impl<'db> TypeInferenceBuilder<'db> { | |||
} | |||
} | |||
|
|||
/// Annotation expressions. | |||
impl<'db> TypeInferenceBuilder<'db> { |
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.
What's the motivation for placing each method in its own impl block?
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 idea was not "each method" but "each set of related methods", though at the moment it is just one method for annotation expressions and one for type expressions; that will change. It was just a way to more clearly separate inference of value expressions from inference of annotation expressions and type expressions.
This was Chris' idea and it seemed fine to me, but I'm also fine with just using comment headers to achieve the same separation.
Summary
Contributes to #12701 by way of helping unlock call expressions (this does not remotely finish that, as it stands, but it gets us moving that direction).
Test Plan
Added a test for function return types which use the name form of an annotation expression, since this is aiming toward call expressions. When we extend this to working for other annotation and type expression positions, we should add explicit tests for those as well.