Skip to content

Reject semantic syntax errors for lazy imports#23757

Merged
charliermarsh merged 2 commits intomainfrom
charlie/lazy-ii
Mar 6, 2026
Merged

Reject semantic syntax errors for lazy imports#23757
charliermarsh merged 2 commits intomainfrom
charlie/lazy-ii

Conversation

@charliermarsh
Copy link
Copy Markdown
Member

Summary

We now reject cases like:

  • lazy import ... inside functions, including async def
  • lazy from ... import ... inside functions, including async def
  • lazy import ... and lazy from ... import ... inside class bodies
  • lazy import ... and lazy from ... import ... anywhere inside a try statement, including except / except*
  • lazy from ... import * anywhere
  • lazy from __future__ import ... anywhere

A follow-up to #23755.

See: #21305.

@charliermarsh charliermarsh added the parser Related to the parser label Mar 6, 2026
@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Mar 6, 2026

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 87.10%. The percentage of expected errors that received a diagnostic held steady at 77.81%.

@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Mar 6, 2026

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Mar 6, 2026

mypy_primer results

No ecosystem changes detected ✅

@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot bot commented Mar 6, 2026

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.

Stmt::ImportFrom(StmtImportFrom {
module, is_lazy, ..
}) => {
// Allow __future__ imports until we see a non-__future__ import.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This comment needs updating. It seems any lazy import acts as import boundary, even if it imports future?

Comment on lines +699 to +704
self.visit_body(body);
for handler in handlers {
self.visit_except_handler(handler);
}
self.visit_body(orelse);
self.visit_body(finalbody);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: Call walk_stmt instead of inlining the visitor code here

python_version: PythonVersion,
source_text: OnceCell<SourceText>,
semantic_checker: SemanticSyntaxChecker,
try_depth: u32,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A simple bool should be sufficient. Before entering a try, store whether we were in a try before, then set it to true. After visiting the try, restore the value to its previous value

ScopeKind::Function => return Some(LazyImportContext::Function),
ScopeKind::Class => return Some(LazyImportContext::Class),
ScopeKind::Comprehension
| ScopeKind::Lambda
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The handling of lambda is inconsistent between Ruff and ty. Ruff returns Function whereas ty returns None. Which is correct? It probably doesn't matter because lambdas can't contain statements. I suggest that you change the match grouping in both implementations to something like:

match ... {
	// Possible, but invalid positions
	ScopeKind::Function => {..},
	ScopeKind::Class => {...},
	// valid positions
	ScopeKind::Module => {},
	// Impossible positions
	ScopeKind::Comprehension | SK::Lambda | SK::TypeAlias | SK::TypeParams => {}

The comments and grouping will help future readers to understand that the last match arms group doesn't really matter at all.

@MichaReiser
Copy link
Copy Markdown
Member

MichaReiser commented Mar 6, 2026

I'm not sure if this is something you did in your previous PR but we should make sure that both ty and Ruff don't treat lazy future import as future imports. We don't need to do this in this PR but seems worth doing now, or we'll forget.

Base automatically changed from charlie/lazy to main March 6, 2026 14:12
@charliermarsh charliermarsh enabled auto-merge (squash) March 6, 2026 14:22
@charliermarsh charliermarsh merged commit 735da8b into main Mar 6, 2026
50 checks passed
@charliermarsh charliermarsh deleted the charlie/lazy-ii branch March 6, 2026 14:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

parser Related to the parser

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants