Skip to content

Generators

A generator function is any function whose body contains a yield statement. Instead of computing a single return value, generators produce a sequence of values lazily — each yield suspends the function and emits one element to the caller.

def count_up(n: int) -> int:
    i = 0
    while i < n:
        yield i
        i += 1

def main():
    for x in count_up(3):
        print(x)  # 0, 1, 2

Syntax

yield_stmt ::= 'yield' expression
             | 'yield' 'from' expression

yield expression

Produces a single value to the caller and suspends the generator.

def squares(n: int) -> int:
    for i in range(n):
        yield i * i

yield from expression

Delegates to another iterable, yielding all of its values in order before continuing with the current generator.

def inner() -> int:
    yield 0
    yield 1

def outer() -> int:
    yield from inner()
    yield 2
    yield 3

# Produces: 0, 1, 2, 3

Return Type Annotation

The return type annotation on a generator specifies the element type, not the collection type. The compiler wraps it automatically:

# Annotate with element type T — compiler produces IEnumerable<T>
def fibonacci(n: int) -> int:
    a = 0
    b = 1
    for i in range(n):
        yield a
        a, b = b, a + b
Context Annotation Emitted C# return type
Standalone function -> T IEnumerable<T>
__iter__ method -> T IEnumerator<T>
__reversed__ method -> T IEnumerator<T>

Early Termination

A bare return (without a value) terminates the generator early:

def take(n: int) -> int:
    i = 0
    while True:
        if i >= n:
            return  # stops iteration
        yield i
        i += 1

Returning a value from a generator is forbidden (see Restrictions).

Generator __iter__ and __reversed__

Using yield inside __iter__ makes a class iterable without writing a separate iterator class:

class Countdown:
    values: list[int]

    def __init__(self, values: list[int]):
        self.values = values

    def __iter__(self) -> int:
        for v in self.values:
            yield v

Similarly, yield inside __reversed__ provides reverse iteration:

class Range:
    start: int
    end: int

    def __init__(self, start: int, end: int):
        self.start = start
        self.end = end

    def __iter__(self) -> int:
        i = self.start
        while i < self.end:
            yield i
            i += 1

    def __reversed__(self) -> int:
        i = self.end - 1
        while i >= self.start:
            yield i
            i -= 1

When __iter__ contains yield, the compiler synthesizes IEnumerable<T> on the class (reported via SPY1001 info diagnostic). When __reversed__ contains yield, IReverseEnumerable<T> is synthesized.

Async Generators

An async def function containing yield is an async generator. It produces an IAsyncEnumerable<T> instead of IEnumerable<T> and can be consumed with async for.

async def async_count(n: int) -> int:
    for i in range(n):
        yield i

async def main():
    async for x in async_count(3):
        print(x)  # 0, 1, 2

yield from in Async Generators

Sharpy extends Python by supporting yield from inside async def functions. Python does not allow yield from in async generators — it requires async for item in iterable: yield item instead. Sharpy lifts this restriction for convenience.

When yield from delegates to a synchronous iterable, the compiler emits a plain foreach loop:

def sync_items() -> int:
    yield 10
    yield 20

async def combined() -> int:
    yield 1
    yield from sync_items()  # foreach + yield return
    yield 2
# Produces: 1, 10, 20, 2

When yield from delegates to an async iterable (IAsyncEnumerable<T>), the compiler emits await foreach:

async def async_source() -> int:
    yield 100
    yield 200

async def combined_async() -> int:
    yield from async_source()  # await foreach + yield return
    yield 300
# Produces: 100, 200, 300

Python deviation: Python 3 forbids yield from in async def functions (SyntaxError). Sharpy allows it as a convenience — the compiler automatically selects foreach or await foreach depending on the iterable type.

Async Generator Return Types

Context Annotation Emitted C# return type
async def with yield -> T IAsyncEnumerable<T>

Yield in Exception Handlers

Due to a C# iterator limitation, yield cannot appear inside try/except/finally blocks. C#'s iterator state machine does not support yield return within exception handling constructs:

def safe_items(items: list[int]) -> int:
    for item in items:
        # ❌ ERROR: yield inside try/except is not allowed
        try:
            yield item
        except:
            pass

    # ✅ OK: yield outside try/except
    for item in items:
        yield item

Workaround: Collect results in a local variable within the try block, then yield after the block:

def safe_items(items: list[int]) -> int:
    for item in items:
        value = 0
        try:
            value = process(item)
        except:
            continue
        yield value

Restrictions

No yield inside __next__

The __next__ method is part of the explicit iterator protocol (manual state management). Combining it with yield (which generates a state machine automatically) is contradictory.

class Bad:
    def __next__(self) -> int:
        yield 1  # ERROR: SPY0268

No return with a value

Generators produce values via yield. A return with a value has no well-defined meaning and is rejected:

def bad_gen() -> int:
    yield 1
    return 42  # ERROR: SPY0267

Use a bare return for early termination instead.

No mixing generator __iter__ with __next__

A class must choose one approach: either a generator-based __iter__ (with yield) or an explicit iterator (with __next__). Defining both is an error:

class Bad:
    def __iter__(self) -> int:
        yield 1  # generator-based
    def __next__(self) -> int:
        return 1  # explicit — ERROR: SPY0269

Nested functions do not propagate

A yield inside a nested function definition does not make the enclosing function a generator:

def outer() -> int:
    def inner() -> int:
        yield 1  # inner is the generator, not outer
    return 42    # outer is a normal function

Diagnostics

Code Level Description
SPY0265 Error yield outside a function
SPY0267 Error return with a value in a generator function
SPY0268 Error yield inside __next__
SPY0269 Error Class has both generator __iter__ and __next__

Implementation

yield expryield return expr; (C# iterator method)

yield from exprforeach (var item in expr) { yield return item; } (delegation via loop)

Bare return in generator → yield break; (early termination)

Generator detection is automatic: the compiler scans for YieldStatement nodes in the function body (excluding nested function/class definitions). Functions containing yield have IsGenerator = true on their symbol.