astral-sh/ruff · critical
Implement Ipy escape command support
Error message
Implement Ipy escape command support
What it means
While checking, ty evaluates expressions in type-expression positions (annotations, base classes, subscripts of generics). If an IPython escape command (%magic, !shell, etc.) lands in such a position — possible when notebook cells are parsed with IPython syntax — inference reaches todo!("Implement Ipy escape command support") and the checker panics rather than degrading. Per the 2025-08 comment in the source, this is the only remaining todo! in ruff/ty, kept loud on purpose.
Source
Thrown at crates/ty_python_semantic/src/types/infer/builder/type_expression.rs:958
format_args!(
"Set comprehensions are not allowed in {}s",
self.type_expression_context()
),
);
Type::unknown()
}
ast::Expr::Generator(generator) => {
if !self.in_string_annotation() {
self.infer_generator_expression(generator, TypeContext::default());
}
self.report_invalid_type_expression(
expression,
format_args!(
"Generator expressions are not allowed in {}s",
self.type_expression_context()
),
);
Type::unknown()
}
ast::Expr::Await(await_expression) => {
if !self.in_string_annotation() {
self.infer_await_expression(await_expression, TypeContext::default());
}
self.report_invalid_type_expression(
expression,
format_args!(
"`await` expressions are not allowed in {}s",
self.type_expression_context()
),
);
Type::unknown()
}
ast::Expr::Yield(yield_expression) => {View on GitHub (pinned to 15f3fe6b15)
Solutions
- Move or remove the magic so no escape command sits in an annotation/type-expression position (put %time on its own line, outside the annotation).
- Update ty and re-test — the todo may since have been implemented or converted into a diagnostic; check the changelog and issue tracker.
- Exclude the affected notebook from checking until support lands (e.g. via respect-excludes / ignore patterns).
- If it reproduces on current ty, file a [ty] issue with the minimal cell contents.
Example fix
# before (notebook cell; magic inside an annotation slot) count: %time int = 0 # after count: int = 0
Defensive patterns
Strategy: validation
Validate before calling
import json, re
from pathlib import Path
MAGIC = re.compile(r'^\s*[%!]\w+')
def notebook_has_magics(path: Path) -> bool:
nb = json.loads(path.read_text())
lines = (
''.join(cell.get('source', []))
for cell in nb.get('cells', [])
if cell.get('cell_type') == 'code'
)
return any(MAGIC.match(line) for chunk in lines for line in chunk.splitlines())
files = [
p for p in map(Path, sys.argv[1:])
if p.suffix != '.ipynb' or not notebook_has_magics(p)
] Try / catch
proc = subprocess.run(['ty', 'check', *files], capture_output=True, text=True)
if proc.returncode != 0 and 'not yet implemented' in proc.stderr:
proc = subprocess.run(
['ty', 'check', *[f for f in files if f.suffix != '.ipynb']],
capture_output=True,
text=True,
) Prevention
- Keep magics on their own lines; never inside annotations, decorators, or type arguments.
- Pin a known-good ty version in CI and upgrade deliberately.
- Check exit code and stderr for 'not yet implemented'/'panicked' so failures are loud, not silent.
When it happens
Trigger: Running ty check on a .ipynb (or a .py parsed with jupyter magics enabled) where a cell places an escape command in a type slot, e.g. 'count: %time int = 0' or a magic on an annotation/decorator line.
Common situations: Notebooks converted to/from scripts where magics drift into annotation positions; cells mixing magics with typed signatures; CI pipelines type-checking raw notebooks.
Related errors
- extra use-def data should have been retained
- Expected a ty `File`, found a ruff `SourceFile`
- InternalError
- should be set because `extract_if` only yields elements with
- Should only ever pass a positive integer to `from_nonnegativ
AI-assisted analysis of astral-sh/ruff@15f3fe6b15 (2026-08-20).
Data as JSON: /api/errors/d2b4589107748b00.
Report an issue: GitHub.