[red-knot] Support unpacking with target#16469
Conversation
f28ac19 to
7dd4ff3
Compare
7dd4ff3 to
7559b1c
Compare
7559b1c to
b9341bb
Compare
| let current_assignment = match optional_vars { | ||
| ast::Expr::Tuple(_) | ast::Expr::List(_) => { | ||
| Some(CurrentAssignment::WithItem { | ||
| item, | ||
| first: true, | ||
| is_async: *is_async, | ||
| unpack: Some(Unpack::new( | ||
| self.db, | ||
| self.file, | ||
| self.current_scope(), | ||
| #[allow(unsafe_code)] | ||
| unsafe { | ||
| AstNodeRef::new(self.module.clone(), optional_vars) | ||
| }, | ||
| UnpackValue::ContextManager(context_manager), | ||
| countme::Count::default(), | ||
| )), | ||
| }) | ||
| } | ||
| ast::Expr::Name(_) => Some(CurrentAssignment::WithItem { | ||
| item, | ||
| is_async: *is_async, | ||
| unpack: None, | ||
| // `false` is arbitrary here---we don't actually use it other than in the actual unpacks | ||
| first: false, | ||
| }), | ||
| ast::Expr::Attribute(ast::ExprAttribute { | ||
| value: object, | ||
| attr, | ||
| .. | ||
| }) => { | ||
| self.register_attribute_assignment( | ||
| object, | ||
| attr, | ||
| AttributeAssignment::ContextManager { context_manager }, | ||
| ); | ||
| None | ||
| } |
There was a problem hiding this comment.
Nit: This code seems to be the same as for for targets with the exception of UnpackValue::ContextManager, AttributeAssignment::ContextManager. Would it be possible to reuse some more code by having a shared method that takes an UnpackKind argument that then constructs the UnpackValue, the AttributeAssignment
There was a problem hiding this comment.
Assign has quite similar code as well, they should probably all three share a helper.
At the very least we should extract repetition around creation of the Unpack, things like the unsafe node-ref creation and the countme::Count::default() feel like details/boilerplate that don't belong at this level of abstraction and shouldn't be repeated for each node kind.
That said, I'd also be open to landing this PR with some duplication if we get all the behavior correct, and then doing the refactor as a separate PR for ease of review.
carljm
left a comment
There was a problem hiding this comment.
Looks great, very impressive first PR!
I didn't get a chance to explore the (lack of) duplicated diagnostics that we discussed on Discord, will try to look at that later.
| let current_assignment = match optional_vars { | ||
| ast::Expr::Tuple(_) | ast::Expr::List(_) => { | ||
| Some(CurrentAssignment::WithItem { | ||
| item, | ||
| first: true, | ||
| is_async: *is_async, | ||
| unpack: Some(Unpack::new( | ||
| self.db, | ||
| self.file, | ||
| self.current_scope(), | ||
| #[allow(unsafe_code)] | ||
| unsafe { | ||
| AstNodeRef::new(self.module.clone(), optional_vars) | ||
| }, | ||
| UnpackValue::ContextManager(context_manager), | ||
| countme::Count::default(), | ||
| )), | ||
| }) | ||
| } | ||
| ast::Expr::Name(_) => Some(CurrentAssignment::WithItem { | ||
| item, | ||
| is_async: *is_async, | ||
| unpack: None, | ||
| // `false` is arbitrary here---we don't actually use it other than in the actual unpacks | ||
| first: false, | ||
| }), | ||
| ast::Expr::Attribute(ast::ExprAttribute { | ||
| value: object, | ||
| attr, | ||
| .. | ||
| }) => { | ||
| self.register_attribute_assignment( | ||
| object, | ||
| attr, | ||
| AttributeAssignment::ContextManager { context_manager }, | ||
| ); | ||
| None | ||
| } |
There was a problem hiding this comment.
Assign has quite similar code as well, they should probably all three share a helper.
At the very least we should extract repetition around creation of the Unpack, things like the unsafe node-ref creation and the countme::Count::default() feel like details/boilerplate that don't belong at this level of abstraction and shouldn't be repeated for each node kind.
That said, I'd also be open to landing this PR with some duplication if we get all the behavior correct, and then doing the refactor as a separate PR for ease of review.
| // `false` is arbitrary here---we don't actually use it other than in the actual unpacks | ||
| first: false, |
There was a problem hiding this comment.
Small preference for going with arbitrary true over arbitrary false here, since it is technically still the first (of one).
But this change would also apply to the same code in For and Assign, so should be made in whichever PR we refactor for better code reuse.
crates/red_knot_python_semantic/src/semantic_index/definition.rs
Outdated
Show resolved
Hide resolved
|
Ok, I looked into the diagnostics situation, and it is going to need some attention here. A bit of background will be needed to understand the issue. We have type inference queries at several granularities: expression (but only some "standalone" expressions, so we don't create too many Salsa ingredients), definition (that is, a value assigned to some name), and scope. We use the definition granularity queries to do lazy type inference of names from scopes we aren't actually checking (e.g. imported libraries). When we are actually checking a file, we run the scope-level inference queries for every scope in the file, and it is those queries where we aggregate and emit diagnostics. So the intent when we run scope-level inference is that as we walk the AST, anywhere we hit a node that is a definition of a name, we use What is currently happening in this PR is that when we find an unpacked with statement in scope-level inference, we don't eagerly run We should add a test like this to demonstrate this: class ContextManager:
def __enter__(self) -> tuple[int, str]:
return (1, "a")
def __exit__(self, exc_type, exc_value, traceback) -> None:
pass
with ContextManager() as (a, b, c):
passThis should emit an error about trying to unpack two elements into three, but currently in this PR it does not. The fix is that this PR needs to update The right fix for this involves fixing astral-sh/ty#185, since this duplicate-definition thing is already broken for Instead, I think the goal for this PR should be to emulate the current handling of Hopefully that all made sense! If not, feel free to ask questions, here or in Discord. Or if it doesn't make sense and you don't have time to wrap your head around it and update the PR, that's totally fine too! Just let me know and one of us can pick it up. |
25d76a3 to
93f3803
Compare
crates/red_knot_python_semantic/src/semantic_index/attribute_assignment.rs
Outdated
Show resolved
Hide resolved
| WithItem { | ||
| item: &'a ast::WithItem, | ||
| first: bool, | ||
| is_async: bool, | ||
| unpack: Option<Unpack<'a>>, | ||
| }, |
There was a problem hiding this comment.
Just for visibility as stated in Discord and it doesn't need to be done in this PR but we could possibly combine first and unpack into a Option<(UnpackPosition, Unpack<'a>)> where the position could be either First or Other. I'm not sure what the fallout of this change would be but that would at least solve the arbitrary boolean problem when the target is a name node.
| fn format_call_dunder_errors( | ||
| error_a: &CallDunderError<'db>, | ||
| name_a: &str, | ||
| error_b: &CallDunderError<'db>, | ||
| name_b: &str, | ||
| ) -> String { |
There was a problem hiding this comment.
nit: I think I'd prefer to make this a closure inside report_diagnostic as I think that's the only place where it's being utilized.
There was a problem hiding this comment.
I think that's true right now, but we could probably use it elsewhere, right?
7e5d772 to
77578aa
Compare
77578aa to
e615e5d
Compare
carljm
left a comment
There was a problem hiding this comment.
Looks great, thank you! Just one failing test with a trivial fix, so I'll push that and then merge.
|
@ericmarkmartin Awesome work! This is a really great first contribution. Thank you for taking this on and also surfacing an important issue related to the unpacking diagnostics. |
* main: [red-knot] Understand `typing.Callable` (#16493) [red-knot] Support unpacking `with` target (#16469) [red-knot] Attribute access and the descriptor protocol (#16416) [`pep8-naming`] Add links to `ignore-names` options in various rules' documentation (#16557) [red-knot] avoid inferring types if unpacking fails (#16530)
Summary
Resolves #16365
Add support for unpacking
withstatement targets.Test Plan
Added some test cases, alike the ones added by #15058.