Skip to content

Fix abuse of return in a with test block #85

Open

Description

@Technologicat

As of 0.15.1, the contents of a with test block are implicitly lifted into a function. This in itself is fine; it is documented behavior, and arguably the simplest way to achieve what the macro needs to do.

The problem is that to declare the expression whose value should be checked to decide the result of the test, the syntax is currently return expr. This is effectively a keyword hijack — in the spirit of pytest's assert rewriting — where a standard Python construct is repurposed to mean something else inside a specific block. Not very pythonic. It conflicts with the established meaning of return and will confuse code analyzers and human readers alike.

Plan

Step 1 — 2.2.0 (non-breaking)

Add an expr macro to declare the tested expression, so the code reads more like standard Python. Name needs some thought:

  • returns[] — closest to intent, but will confuse humans as to why there is a separate returns vs. the standard return.
  • result[], results_in[], evaluates_into[] — semantically slightly off; we want to declare a check, not a result.
  • check[] — checkmate in t(h)ree? Probably the best so far.
  • Whatever the final name, it should be compact (preferably one short-ish word) while making the intent blindingly obvious.

During 2.2.0, using return expr inside a with test block continues to work, but emits a DeprecationWarning suggesting the new macro.

Step 2 — 3.0.0 (breaking)

Let return inside a with test block have its usual Python meaning (return from the enclosing function). unpythonic aims to be as pythonic as possible; undoing the keyword hijack — not banning return — is the point.

This likely means the with test: body can no longer be implicitly lifted to a function (or the lift has to transparently propagate return out to the enclosing function). Implementation details TBD — tracked here because the user-visible end state is clear even if the mechanism needs design work.

Scope note

Local variables assigned inside with test: would still be test-local under the current implementation (scope boundary from the implicit lift), which is acceptable precedent — comprehensions and generator expressions do the same, as do unpythonic's continuations. If a variable needs to be visible in the parent scope, use a box (or nonlocal / global as appropriate).

Activity

  1. added this to the 0.15.x milestone on Jan 31, 2022
  2. modified the milestones: 1.x, 2.2.0 on Apr 24, 2026
  3. Technologicat commented on May 5, 2026

    @Technologicat
    OwnerAuthor

    Step 1 shipped in 2.2.0 (commit `7034add`, master).

    Summary of what landed:

    • New expr macro `expect[expr]` for use as a top-level statement of a `with test:` block. Declares the expression whose truthiness is asserted. Implemented in `unpythonic.syntax.testingtools`.
    • The older `return expr` form continues to work unchanged but emits a `DeprecationWarning` at macro-expansion time, with the user-visible filename and line of the offending `return`.
    • At most one `expect[]` per block, and `expect[]` and `return` cannot coexist in the same block — both diagnosed as `SyntaxError` at macro-expansion time.
    • Capture rules (implicit LHS `the[]` on a top-level `Compare`, explicit `the[]` marks) apply to the expression inside `expect[]` exactly as they did to `return expr`.

    Naming choice: tried `returns[]`, `result[]`, `evaluates_into[]`, `check[]` per the ticket's draft list — `expect[]` reads most cleanly at the use site (`expect[x == 42]` parses naturally as English) and avoids the `returns`/`return` collision.

    Step 2 (3.0.0): un-hijack `return` — still open. Re-milestoning this issue to 3.0.0; the ticket already documents the full plan, so keeping it as a single issue from start to finish.

    — Claude (Opus 4.7)

  4. modified the milestones: 2.2.0, 3.0.0 on May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions