Default to latest supported Python version for version-related syntax errors#17529
Default to latest supported Python version for version-related syntax errors#17529
Conversation
CodSpeed Performance ReportMerging #17529 will not alter performanceComparing Summary
|
|
| code | total | + violation | - violation | + fix | - fix |
|---|---|---|---|---|---|
| SyntaxError: | 6 | 0 | 6 | 0 | 0 |
This comment was marked as resolved.
This comment was marked as resolved.
|
I haven't started reviewing the PR yet but just a remark on the design. My only concern with this approach is that it won't work for Red Knot because it currently always requires to know exactly which Python version it is checking. Having said that, I'm okay with ignoring this limitation for now because we may be able to lift this Red Knot restriction in the future by supporting multi-version checking. |
0b235d0 to
9cdc98f
Compare
|
As mentioned above, I updated the Thanks for the additional context on red-knot's approach. I think this is ready for review if the design sounds reasonable otherwise. |
|
I think I'd find it useful to have a small write up that walks through the different places where we now change the default and explains why it's important that we pick a different default and why that specific default. I've a slight concern that we now have multiple defaults and it's unclear to even me when we pick which one. I worry that it will be very hard for users to understand what's going on and even harder for us to make this self explanatory in the tool (e.g. by attaching notes in diagnostics). Could we come up with a simpler approach that assumes fewer different versions or maybe simply disables certain checks if no version's specified? |
|
I think this should now be up to date with our internal discussions. In summary (I'll update the PR summary to something like this once this is up to date):
TODOsI left two literal TODOs in My other question is: should we go ahead and merge this? I put the |
I don't think we need to change anything here (assuming they still default to lowest supported)
I think landing this without breaking should be fine because it only impacts version-specific syntax and semantic errors, both are behind preview. |
Yep, they still
Ah of course, I keep forgetting they're still in preview. Thanks! |
MichaReiser
left a comment
There was a problem hiding this comment.
Overall looks good but I think we should try to find a better place for the default handling. I'm worried by the amount of places where we now have to call unwrap_or_default.
| comment_ranges: &CommentRanges, | ||
| settings: &LinterSettings, | ||
| target_version: PythonVersion, | ||
| target_version: Option<PythonVersion>, |
There was a problem hiding this comment.
It would be nice if we could handle the default elsewhere. E.g. on the call site. I would prefer to have the default handling in as few places as possible.
There was a problem hiding this comment.
I was able to move the unwrap in this case up to check_path, but I went through the others and didn't see any more we could fix. Anything that goes through check_ast needs to have the Option because check_ast constructs a Checker, which also contains an Option. That brings us down to only two unwrap_or_default calls, both in check_path.
I think the unwrap_or_latest calls are as restricted as possible too, with only four of them in non-test code, and two of those are outside of the linter in ruff_wasm and ruff_server.
I agree in general, though, so I'm happy to update more of these if I'm missing a nicer solution.
There was a problem hiding this comment.
Actually, maybe we could make ParseOptions::with_target_version take an option and handle the unwrap once internally, if that would be preferable. I haven't tried it yet, but I don't think that would cause problems anywhere.
There was a problem hiding this comment.
I'm not too concerned about passing Option. That's just what we need to do. I'm more concerned about the number of places where we need to change to get consistent behavior. It seems very easy to miss updating one of those places.
Actually, maybe we could make ParseOptions::with_target_version take an option and handle the unwrap once internally, if that would be preferable
I think I'd prefer changing the ParseOptions::default to initialize to the latest version and to either:
- Only call
with_target_versioniftarget_versionisSome - Change
with_target_versionto take anOptionand only set it if it isn'tNone
Unless changing the default results in a too big fall out with tests?
|
You also need to update the PR summary. |
Will do! I was just waiting to see if there were any other big changes before I rewrote it. |
| comment_ranges: &CommentRanges, | ||
| settings: &LinterSettings, | ||
| target_version: PythonVersion, | ||
| target_version: Option<PythonVersion>, |
There was a problem hiding this comment.
I'm not too concerned about passing Option. That's just what we need to do. I'm more concerned about the number of places where we need to change to get consistent behavior. It seems very easy to miss updating one of those places.
Actually, maybe we could make ParseOptions::with_target_version take an option and handle the unwrap once internally, if that would be preferable
I think I'd prefer changing the ParseOptions::default to initialize to the latest version and to either:
- Only call
with_target_versioniftarget_versionisSome - Change
with_target_versionto take anOptionand only set it if it isn'tNone
Unless changing the default results in a too big fall out with tests?
crates/ruff_linter/src/linter.rs
Outdated
| comment_ranges, | ||
| settings, | ||
| target_version, | ||
| target_version.unwrap_or_default(), |
There was a problem hiding this comment.
Would it help if check_path (et al) would take a TargetVersion struct instead that has two methods (or even in LinterSettings or for all tools?):
linter_versionparser_version- (
formatter_version)
It's a thin wrapper around an Option<PythonVersion>
It would give us a place to document the decision why we use one version for a specific tool and it doesn't repeat the decision in many different places.
That makes me wonder if we should also pass this struct to the formatter and analyze so that they can initialize the parser with the right version.
There was a problem hiding this comment.
Oh yeah, I think that's a great idea. That was roughly my intention earlier with two Checker methods, but the parser didn't get a whole Checker, so I couldn't get it to work out nicely. Wrapping Option<PythonVersion> seems like it would help a lot.
That makes me wonder if we should also pass this struct to the formatter and analyze so that they can initialize the parser with the right version.
Ooh good catch. Although it may not be a big deal in these cases if they aren't emitting the errors. It looks like both of these don't actually pass any Python version to the parser currently, so it's just using the default.
|
Could I get one more quick review here? I think the I think the three (minor) things I'm not sure about are
We could also reuse As an even smaller note, I know we usually prefer calling |
MichaReiser
left a comment
There was a problem hiding this comment.
Looks good. Thanks for following up on this.
I haven't used display_settings myself but I think it also supports calling the debug implementation instead of Display. But I think what you have is fine.
| /// Note that this method should not be used for version-related syntax errors emitted by the | ||
| /// parser or the [`SemanticSyntaxChecker`], which should instead default to the _latest_ | ||
| /// supported Python version. | ||
| pub(crate) fn target_version(&self) -> PythonVersion { |
There was a problem hiding this comment.
I agree that this method is still useful. It's somewhat confusing that it's called target-version and returns a PythonVersion but renaming it to python_version might be equally confusing because of the method on SemanticSyntaxContext.
|
I double-checked the ecosystem results, and chameleon has a pyproject.toml but no project-level |
…cialization * origin/main: Default to latest supported Python version for version-related syntax errors (#17529)
… errors (astral-sh#17529) ## Summary This PR partially addresses astral-sh#16418 via the following: - `LinterSettings::unresolved_python_version` is now a `TargetVersion`, which is a thin wrapper around an `Option<PythonVersion>` - `Checker::target_version` now calls `TargetVersion::linter_version` internally, which in turn uses `unwrap_or_default` to preserve the current default behavior - Calls to the parser now call `TargetVersion::parser_version`, which calls `unwrap_or_else(PythonVersion::latest)` - The `Checker`'s implementation of `SemanticSyntaxContext::python_version` also uses `TargetVersion::parser_version` to use `PythonVersion::latest` for semantic errors In short, all lint rule behavior should be unchanged, but we default to the latest Python version for the new syntax errors, which should minimize confusing version-related syntax errors for users without a version configured. ## Test Plan Existing tests, which showed no changes (except for printing default settings).
Summary
This PR partially addresses #16418 via the following:
LinterSettings::unresolved_python_versionis now aTargetVersion, which is a thin wrapper around anOption<PythonVersion>Checker::target_versionnow callsTargetVersion::linter_versioninternally, which in turn usesunwrap_or_defaultto preserve the current default behaviorTargetVersion::parser_version, which callsunwrap_or_else(PythonVersion::latest)Checker's implementation ofSemanticSyntaxContext::python_versionalso usesTargetVersion::parser_versionto usePythonVersion::latestfor semantic errorsIn short, all lint rule behavior should be unchanged, but we default to the latest Python version for the new syntax errors, which should minimize confusing version-related syntax errors for users without a version configured.
Test Plan
Existing tests, which showed no changes (except for printing default settings).