Skip to content

Conversation

@maxmynter
Copy link
Contributor

Closes: astral-sh/ty#299

Summary

Don't warn yield not in function when yield is in function.

This is achieved by iterating through the scope stack to check if it contains a function scope.

The issue was found when creating the semantic syntax md integration tests (#17748 (comment)).

Test Plan

Updated integration tests to reflect this behavior.


I don't know if I fully get how issues / PR's work across the boundaries of ruff and ty. Let me know if i'm doing something wrong.

and update the integration tests for this behavior.
@github-actions
Copy link
Contributor

github-actions bot commented May 10, 2025

mypy_primer results

No ecosystem changes detected ✅

@dylwil3 dylwil3 added the ty Multi-file analysis & type inference label May 11, 2025
@dylwil3 dylwil3 changed the title Don't warn yield not in function when yield is in function [ty] Don't warn yield not in function when yield is in function May 11, 2025
for scope_info in self.scope_stack.iter().rev() {
let scope = &self.scopes[scope_info.file_scope_id];
match scope.kind() {
ScopeKind::Lambda | ScopeKind::Function => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should also return false if we see a ScopeKind::Class:

>>> def f():
...     class C:
...         yield 5
...
  File "<python-input-1>", line 3
    yield 5
    ^^^^^^^
SyntaxError: 'yield' outside function

I'm also realizing that I gave bad advice in my comment on the issue. The in_*_scope methods are supposed to refer to the directly-enclosing scope:

/// Note that the `in_*_scope` methods should refer to the immediately-enclosing scope. For example,
/// `in_function_scope` should return true for this case:
///
/// ```python
/// def f():
/// x # here
/// ```
///
/// but not for this case:
///
/// ```python
/// def f():
/// class C:
/// x # here
/// ```

We may just want to reuse in_await_allowed_context for detecting this syntax error and also possibly rename it to something like in_function_context.

That should fix the ruff implementation of the rule too, which I guess I hadn't seen issues with either.

@AlexWaygood AlexWaygood added the diagnostics Related to reporting of diagnostics. label May 12, 2025
@github-actions
Copy link
Contributor

github-actions bot commented May 13, 2025

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

@maxmynter
Copy link
Contributor Author

All right, this took longer than I thought for a quite small diff.

  • Reverted the change to in_function_scope to only consider the immediate scope.

  • I'm on the fence with renaming in_await_allowed_context. Especially considering the contextual comment in the trait definition it seems the current name better conveys what the function does.

  • The upward scope stack traversal in in_await_allowed_context needs to be checked against generator and comprehensions to not give false positives/negatives.

  • Rename in_sync_comprehension to in_sync_comprehension_scope for consistency with other locator methods in scope.

Questions:

  • How important is performance here? Specifically, how expensive is traversing the scope stack? We can avoid the duplicate call of is_await_allowed_context at some cost of readability with more nesting. I've already ordered the early returns in increasing expensiveness.

@maxmynter
Copy link
Contributor Author

The notebook in the ecosystem changes gives the same Invalid JSON error when seen through the Git web ui.

@AlexWaygood AlexWaygood removed their request for review May 13, 2025 02:11
@sharkdp sharkdp removed their request for review May 13, 2025 11:59
@maxmynter maxmynter requested a review from ntBre May 13, 2025 22:53
Comment on lines 790 to 792
if !ctx.in_sync_comprehension_scope()
&& !ctx.in_generator_scope()
&& ctx.in_await_allowed_context()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit strange because in_sync_comprehension_scope and in_await_allowed_context both traverse the parent scopes, but in_generator_scope doesn't. Is there a way to break this check by nesting generators somehow?

I think I would probably lean toward a separate context method anyway, just for clarity. Traversing scopes should be pretty cheap since it's just iterating backwards over a vector for both ruff and ty, but I think this whole check would be simplified if we had an in_yield_allowed_context method or similar. And then we can traverse the stack once anyway.

Ah, actually I think that's the reason for the else-if on kind.is_await. If we don't return from there, we'll run all of these checks again, even though we already know we want a diagnostic for await. So I think I would put this whole section back to something like this:

if kind.is_await { 
    // ...
} else if ctx.in_yield_allowed_context() {
    return
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Adressed in 1d0e43b

I was shying away from adding a new method to the trait because then the implementation in crates/ruff_linter/src/checkers/ast/mod.rs isn't used.

But I now agree it's not that bad.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also add a test case for ruff (is that what you mean by the Checker version not being used?)

@ntBre
Copy link
Contributor

ntBre commented May 14, 2025

The notebook in the ecosystem changes gives the same Invalid JSON error when seen through the Git web ui.

Yeah no worries about this, I've been seeing it on every PR. If the issue persists we can ignore the file from the ecosystem check.

Edit: looks like they fixed it 6 hours ago. Next time the ecosystem check runs it should update!

@maxmynter
Copy link
Contributor Author

It seems that backticked parts were sliced out of the commit messages.

They were supposed to be

  • Drop _scope from in_sync_comprehension_scope (a62a990)

  • (refactor) Add in_yield_allowed_context to SemanticSyntaxContext trait (1d0e43b)

But since we squash, i'll leave them as is.

@maxmynter maxmynter requested a review from ntBre May 14, 2025 20:31
Copy link
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks good to me! The only additional thing would be adding a test in ruff. I think it could have been susceptible to the same, or at least similar, initial issue. But the existing tests should at least exercise the new context method anyway.

It might be good to get a quick review from someone on the ty side too, but this is otherwise good to go.

@maxmynter
Copy link
Contributor Author

Added tests in 7460049

@maxmynter
Copy link
Contributor Author

maxmynter commented May 17, 2025

Ping @carljm @dhruvmanila @MichaReiser (because Brent said it would be good if someone from the TY side would review and you're code owners and recent ty committers :) )

@MichaReiser MichaReiser merged commit 02fd481 into astral-sh:main May 21, 2025
35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

diagnostics Related to reporting of diagnostics. ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Don't say that a yield statement in an annotation scope in a function is "not in a function"

5 participants