-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[ty] Infer nonlocal types as unions of all reachable bindings #18750
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
Conversation
|
a0e9746
to
70742b3
Compare
## Summary As far as I can tell, the two existing tests did the exact same thing. Remove the redundant test, and add tests for all combinations of declared/not-declared and local/"public" use of the name. Proposing this as a separate PR before the behavior might change via #18750
70742b3
to
1af17e0
Compare
CodSpeed WallTime Performance ReportMerging #18750 will not alter performanceComparing Summary
|
This comment was marked as off-topic.
This comment was marked as off-topic.
c173689
to
403108d
Compare
00fda72
to
78d8b69
Compare
@@ -1651,7 +1651,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
if let Some(builder) = self.context.report_lint(&CONFLICTING_DECLARATIONS, node) { | |||
builder.into_diagnostic(format_args!( | |||
"Conflicting declared types for `{place}`: {}", | |||
conflicting.display(db) | |||
conflicting.iter().map(|ty| ty.display(db)).join(", ") |
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.
Can't easily get this to work with TypeArrayDisplay
, so just join it manually.
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 added this function yesterday, which could possibly be generalised:
ruff/crates/ty_python_semantic/src/types/diagnostic.rs
Lines 2039 to 2064 in c1fed55
/// List the problematic class bases in a human-readable format. | |
fn describe_problematic_class_bases(&self, db: &dyn Db) -> String { | |
let num_bases = self.len(); | |
debug_assert!(num_bases >= 2); | |
let mut bad_base_names = self.0.values().map(|info| info.originating_base.name(db)); | |
let final_base = bad_base_names.next_back().unwrap(); | |
let penultimate_base = bad_base_names.next_back().unwrap(); | |
let mut buffer = String::new(); | |
for base_name in bad_base_names { | |
buffer.push('`'); | |
buffer.push_str(base_name); | |
buffer.push_str("`, "); | |
} | |
buffer.push('`'); | |
buffer.push_str(penultimate_base); | |
buffer.push_str("` and `"); | |
buffer.push_str(final_base); | |
buffer.push('`'); | |
buffer | |
} |
its behaviour is that if you have a list of two types A
and B
, it creates the string "`A` and `B`"
, if you have a list of three types A
, B
and C
, it creates the string "`A`, `B` and `C`"
, for four types it creates the string "`A`, `B`, `C` and `D`"
, etc.
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 guess a generalised version would be something like this (untested):
fn describe_types_list<'db, I, IT, D>(db: &'db dyn Db, types: I) -> String
where
I: IntoIterator<IntoIter = IT>,
IT: ExactSizeIterator<Item = D> + DoubleEndedIterator,
D: std::fmt::Display,
{
let types = types.into_iter();
debug_assert!(types.len() >= 2);
let final_type = types.next_back().unwrap();
let penultimate_type = types.next_back().unwrap();
let mut buffer = String::new();
for type in types {
buffer.push('`');
buffer.push_str(type);
buffer.push_str("`, ");
}
buffer.push('`');
buffer.push_str(penultimate_type);
buffer.push_str("` and `");
buffer.push_str(final_type);
buffer.push('`');
buffer
}
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.
if you have a list of three types
A
,B
andC
, it creates the string"`A`, `B` and `C`"
, for four types it creates the string"`A`, `B`, `C` and `D`"
, etc.
I see we are Oxford comma nemeses... 🐂
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.
Thank you, Alex. I'll keep this for a follow-up.
This comment was marked as resolved.
This comment was marked as resolved.
0c77760
to
d055a0d
Compare
d1c15ca
to
4226670
Compare
* main: [ty] Add regression-benchmark for attribute-assignment hang (#18957) [ty] Format conflicting types as an enumeration (#18956) [ty] Prevent union builder construction for just one declaration (#18954) [ty] Infer nonlocal types as unions of all reachable bindings (#18750) [`pyflakes`] Mark `F504`/`F522`/`F523` autofix as unsafe if there's a call with side effect (#18839) [`playground`] Add ruff logo docs link to Header.tsx (#18947) [ty] Reduce the overwhelming complexity of `TypeInferenceBuilder::infer_call_expression` (#18943) [ty] Add subdiagnostic about empty bodies in more cases (#18942) [ty] Move search path resolution to `Options::to_program_settings` (#18937) [`flake8-errmsg`] Extend `EM101` to support byte strings (#18867) Move big rule implementations (#18931) [`pylint`] Allow fix with comments and document performance implications (`PLW3301`) (#18936)
Summary
This PR includes a behavioral change to how we infer types for nonlocal uses of symbols within a module. Where we would previously use the type that a use at the end of the scope would see, we now consider all reachable bindings and union the results:
This helps especially in cases where the the end of the scope is not reachable:
This PR also proposes to skip the boundness analysis of nonlocal uses. This is consistent with the "all reachable bindings" strategy, because the implicit
x = <unbound>
binding is also always reachable, and we would have to emit "possibly-unresolved" diagnostics for every nonlocal use otherwise. Changing this behavior allows common use-cases like the following to type check without any errors:closes astral-sh/ty#210
closes astral-sh/ty#607
closes astral-sh/ty#699
Follow up
It is now possible to resolve the following TODO, but I would like to do that as a follow-up, because it requires some changes to how we treat implicit attribute assignments, which could result in ecosystem changes that I'd like to see separately.
ruff/crates/ty_python_semantic/src/semantic_index/builder.rs
Lines 1095 to 1117 in 315fb0f
Ecosystem analysis
Full report
possibly-unresolved-reference
diagnostics (7818) because we do not analyze boundness for nonlocal uses of symbols inside modules anymore.unresolved-reference
diagnostics (231) in scenarios like this:Test Plan
New Markdown tests