Skip to content

Commit 465d579

Browse files
antonsyndclaude
andcommitted
docs: add generator/yield to dogfood prompts and language spec
- Add generators.md language specification covering yield, yield from, generator __iter__/__reversed__, restrictions, and diagnostics - Update dogfood prompts with generator syntax, examples, and forbidden patterns (yield in __next__, return-with-value, iter/next conflict) - Add generator feature focuses to dogfood orchestrator tier 3 - Add dunder_reversed to dogfood orchestrator tier 2 - Add category mappings for new generator/reversed features Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 12d8609 commit 465d579

5 files changed

Lines changed: 278 additions & 2 deletions

File tree

build_tools/sharpy_dogfood/convert.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ def get_category_from_feature(feature_focus: Optional[str]) -> str:
173173
"dunder_bool": "classes",
174174
"dunder_len": "classes",
175175
"dunder_iter": "classes",
176+
"dunder_reversed": "classes",
176177
"dunder_operators": "classes",
177178
"dunder_comparison": "classes",
178179
"dunder_unary": "classes",
@@ -212,6 +213,12 @@ def get_category_from_feature(feature_focus: Optional[str]) -> str:
212213
"named_tuple": "named_tuples",
213214
# Comparison Chaining
214215
"comparison_chaining": "type_system",
216+
# Generators & Yield
217+
"generator_basic": "generators",
218+
"generator_yield_from": "generators",
219+
"generator_early_return": "generators",
220+
"generator_iter_class": "generators",
221+
"generator_reversed_class": "generators",
215222
}
216223

217224
return category_map.get(feature_focus, "misc")

build_tools/sharpy_dogfood/orchestrator.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,7 @@ def _replace_expected_output_in_code(code: str, new_output: str) -> str:
523523
"dunder_bool",
524524
"dunder_len",
525525
"dunder_iter",
526+
"dunder_reversed",
526527
"dunder_operators",
527528
"dunder_comparison",
528529
"dunder_unary",
@@ -568,6 +569,12 @@ def _replace_expected_output_in_code(code: str, new_output: str) -> str:
568569
"named_tuple",
569570
# Comparison Chaining
570571
"comparison_chaining",
572+
# Generators & Yield
573+
"generator_basic",
574+
"generator_yield_from",
575+
"generator_early_return",
576+
"generator_iter_class",
577+
"generator_reversed_class",
571578
# Feature Combinations (advanced)
572579
"nested_if_in_loop",
573580
"loop_in_function",

build_tools/sharpy_dogfood/prompts.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ def _get_remediation_hint(validation_error: str) -> str:
146146
- **`__eq__(self, other) -> bool`** / **`__hash__(self) -> int`**: Equality and hashing (must define both or neither)
147147
- **`__bool__(self) -> bool`**: Truthiness in `if` statements (synthesizes `IBoolConvertible`)
148148
- **`__len__(self) -> int`**: Enables `len(obj)` (synthesizes `ISized`)
149-
- **`__iter__(self)`** / **`__next__(self)`**: Iterator protocol, enables `for item in obj:`
149+
- **`__iter__(self)`** / **`__next__(self)`**: Iterator protocol, enables `for item in obj:`. Prefer `yield` inside `__iter__` over manual `__next__`.
150+
- **`__reversed__(self)`**: Reverse iteration, enables `reversed(obj)`. Use `yield` to produce values in reverse order.
150151
- **Arithmetic operators**: `__add__`, `__sub__`, `__mul__`, `__div__`, `__mod__` → `+`, `-`, `*`, `/`, `%`
151152
- **Bitwise operators**: `__and__`, `__or__`, `__xor__`, `__lshift__`, `__rshift__`
152153
- **Comparison operators**: `__lt__`, `__le__`, `__gt__`, `__ge__`, `__ne__`
@@ -305,6 +306,17 @@ def _get_remediation_hint(validation_error: str) -> str:
305306
- **IMPORTANT**: The receiving function MUST declare its parameter with a function type: `def apply(fn: (int) -> int) -> int:`
306307
- **WARNING**: Lambdas CANNOT be assigned to `auto` variables — there is no type context to infer parameter types. Use an explicit function type: `square: (int) -> int = lambda n: n * n`, NOT `square: auto = lambda n: n * n`.
307308
309+
#### Generators & Yield
310+
- **Generator function**: Any function containing `yield` becomes a generator — it returns `IEnumerable<T>` and can be iterated with `for x in gen():`
311+
- **Yield statement**: `yield value` produces a value to the caller, suspending the generator
312+
- **Yield from**: `yield from other_generator()` delegates to another generator, yielding all its values
313+
- **Generator return type**: Annotate with the ELEMENT type, not the collection type: `def count() -> int:` (not `-> IEnumerable<int>`)
314+
- **Early return in generators**: `return` (without a value) terminates the generator early. `return value` in a generator is FORBIDDEN.
315+
- **Generator __iter__**: Use `yield` inside `__iter__(self)` to make a class iterable: `def __iter__(self) -> int: yield 1; yield 2`
316+
- **Generator __reversed__**: Use `yield` inside `__reversed__(self)` for reverse iteration: `def __reversed__(self) -> int: ...`
317+
- **IMPORTANT**: `yield` CANNOT appear inside `__next__()` — it is only allowed in regular functions, `__iter__`, and `__reversed__`
318+
- **IMPORTANT**: A class CANNOT define both generator `__iter__` (with yield) AND `__next__` — these are mutually exclusive approaches
319+
308320
#### Optional Types (0.1.15)
309321
- **Optional type**: `x: int? = Some(42)`, `y: int? = None()`
310322
- **Optional constructors**: `Some(value)` wraps a value, `None()` represents absence
@@ -342,7 +354,10 @@ def _get_remediation_hint(validation_error: str) -> str:
342354
- **NO `__repr__()` method**: removed — only `__str__()` exists (maps to `.ToString()`)
343355
- **NO `del` statement**: `del x` — not supported
344356
- **NO `**kwargs` spreading in function calls**: `func(**kwargs)` — not yet supported for keyword argument spreading
345-
- **NO spread in non-variadic function calls**: `func(*args)` only works when the function has `*args` parameter or when spreading a tuple that matches exact parameter count"""
357+
- **NO spread in non-variadic function calls**: `func(*args)` only works when the function has `*args` parameter or when spreading a tuple that matches exact parameter count
358+
- **NO `yield` inside `__next__`**: `yield` is only allowed in regular functions, `__iter__`, and `__reversed__`
359+
- **NO `return value` in generators**: Generators cannot return a value — use bare `return` for early termination
360+
- **NO mixing generator `__iter__` with `__next__`**: A class cannot have both `yield`-based `__iter__` AND a `__next__` method"""
346361

347362
NAMING_RULES_SECTION = """\
348363
### ⚠️ CRITICAL NAMING RULES - Avoid builtin conflicts:
@@ -638,6 +653,43 @@ def __init__(self, w: float, h: float):
638653
# Comparison chaining
639654
if 0 < x < 10:
640655
print("in range")
656+
657+
# Generator function with yield
658+
def count_up(n: int) -> int:
659+
i = 0
660+
while i < n:
661+
yield i
662+
i += 1
663+
664+
for x in count_up(3):
665+
print(x) # 0, 1, 2
666+
667+
# Yield from delegation
668+
def inner() -> int:
669+
yield 1
670+
yield 2
671+
672+
def outer() -> int:
673+
yield 0
674+
yield from inner()
675+
676+
# Class with generator __iter__ and __reversed__
677+
class Range:
678+
start: int
679+
end: int
680+
def __init__(self, start: int, end: int):
681+
self.start = start
682+
self.end = end
683+
def __iter__(self) -> int:
684+
i = self.start
685+
while i < self.end:
686+
yield i
687+
i += 1
688+
def __reversed__(self) -> int:
689+
i = self.end - 1
690+
while i >= self.start:
691+
yield i
692+
i -= 1
641693
```
642694
643695
## Output Format

docs/language_specification/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ See [function_variadic_arguments.md](function_variadic_arguments.md) for variadi
185185

186186
See [parameter_modifiers.md](parameter_modifiers.md) for `ref`, `out`, and `in` pass-by-reference parameters.
187187

188+
See [generators.md](generators.md) for generator functions, `yield`, `yield from`, and generator `__iter__`/`__reversed__`.
189+
188190
---
189191

190192
## Classes
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
# Generators
2+
3+
A generator function is any function whose body contains a `yield` statement.
4+
Instead of computing a single return value, generators produce a sequence of
5+
values lazily — each `yield` suspends the function and emits one element to the
6+
caller.
7+
8+
```python
9+
def count_up(n: int) -> int:
10+
i = 0
11+
while i < n:
12+
yield i
13+
i += 1
14+
15+
def main():
16+
for x in count_up(3):
17+
print(x) # 0, 1, 2
18+
```
19+
20+
## Syntax
21+
22+
```
23+
yield_stmt ::= 'yield' expression
24+
| 'yield' 'from' expression
25+
```
26+
27+
### `yield expression`
28+
29+
Produces a single value to the caller and suspends the generator.
30+
31+
```python
32+
def squares(n: int) -> int:
33+
for i in range(n):
34+
yield i * i
35+
```
36+
37+
### `yield from expression`
38+
39+
Delegates to another iterable, yielding all of its values in order before
40+
continuing with the current generator.
41+
42+
```python
43+
def inner() -> int:
44+
yield 0
45+
yield 1
46+
47+
def outer() -> int:
48+
yield from inner()
49+
yield 2
50+
yield 3
51+
52+
# Produces: 0, 1, 2, 3
53+
```
54+
55+
## Return Type Annotation
56+
57+
The return type annotation on a generator specifies the **element type**, not
58+
the collection type. The compiler wraps it automatically:
59+
60+
```python
61+
# Annotate with element type T — compiler produces IEnumerable<T>
62+
def fibonacci(n: int) -> int:
63+
a = 0
64+
b = 1
65+
for i in range(n):
66+
yield a
67+
a, b = b, a + b
68+
```
69+
70+
| Context | Annotation | Emitted C# return type |
71+
|---------|-----------|------------------------|
72+
| Standalone function | `-> T` | `IEnumerable<T>` |
73+
| `__iter__` method | `-> T` | `IEnumerator<T>` |
74+
| `__reversed__` method | `-> T` | `IEnumerator<T>` |
75+
76+
## Early Termination
77+
78+
A bare `return` (without a value) terminates the generator early:
79+
80+
```python
81+
def take(n: int) -> int:
82+
i = 0
83+
while True:
84+
if i >= n:
85+
return # stops iteration
86+
yield i
87+
i += 1
88+
```
89+
90+
Returning a value from a generator is forbidden (see [Restrictions](#restrictions)).
91+
92+
## Generator `__iter__` and `__reversed__`
93+
94+
Using `yield` inside `__iter__` makes a class iterable without writing a
95+
separate iterator class:
96+
97+
```python
98+
class Countdown:
99+
values: list[int]
100+
101+
def __init__(self, values: list[int]):
102+
self.values = values
103+
104+
def __iter__(self) -> int:
105+
for v in self.values:
106+
yield v
107+
```
108+
109+
Similarly, `yield` inside `__reversed__` provides reverse iteration:
110+
111+
```python
112+
class Range:
113+
start: int
114+
end: int
115+
116+
def __init__(self, start: int, end: int):
117+
self.start = start
118+
self.end = end
119+
120+
def __iter__(self) -> int:
121+
i = self.start
122+
while i < self.end:
123+
yield i
124+
i += 1
125+
126+
def __reversed__(self) -> int:
127+
i = self.end - 1
128+
while i >= self.start:
129+
yield i
130+
i -= 1
131+
```
132+
133+
When `__iter__` contains `yield`, the compiler synthesizes `IEnumerable<T>` on
134+
the class (reported via SPY1001 info diagnostic). When `__reversed__` contains
135+
`yield`, `IReverseEnumerable<T>` is synthesized.
136+
137+
## Restrictions
138+
139+
### No `yield` inside `__next__`
140+
141+
The `__next__` method is part of the *explicit* iterator protocol (manual state
142+
management). Combining it with `yield` (which generates a state machine
143+
automatically) is contradictory.
144+
145+
```python
146+
class Bad:
147+
def __next__(self) -> int:
148+
yield 1 # ERROR: SPY0268
149+
```
150+
151+
### No `return` with a value
152+
153+
Generators produce values via `yield`. A `return` with a value has no
154+
well-defined meaning and is rejected:
155+
156+
```python
157+
def bad_gen() -> int:
158+
yield 1
159+
return 42 # ERROR: SPY0267
160+
```
161+
162+
Use a bare `return` for early termination instead.
163+
164+
### No mixing generator `__iter__` with `__next__`
165+
166+
A class must choose one approach: either a generator-based `__iter__` (with
167+
`yield`) or an explicit iterator (with `__next__`). Defining both is an error:
168+
169+
```python
170+
class Bad:
171+
def __iter__(self) -> int:
172+
yield 1 # generator-based
173+
def __next__(self) -> int:
174+
return 1 # explicit — ERROR: SPY0269
175+
```
176+
177+
### Nested functions do not propagate
178+
179+
A `yield` inside a nested function definition does not make the enclosing
180+
function a generator:
181+
182+
```python
183+
def outer() -> int:
184+
def inner() -> int:
185+
yield 1 # inner is the generator, not outer
186+
return 42 # outer is a normal function
187+
```
188+
189+
## Diagnostics
190+
191+
| Code | Level | Description |
192+
|------|-------|-------------|
193+
| SPY0265 | Error | `yield` outside a function |
194+
| SPY0267 | Error | `return` with a value in a generator function |
195+
| SPY0268 | Error | `yield` inside `__next__` |
196+
| SPY0269 | Error | Class has both generator `__iter__` and `__next__` |
197+
198+
## Implementation
199+
200+
*`yield expr``yield return expr;` (C# iterator method)*
201+
202+
*`yield from expr``foreach (var item in expr) { yield return item; }` (delegation via loop)*
203+
204+
*Bare `return` in generator → `yield break;` (early termination)*
205+
206+
*Generator detection is automatic: the compiler scans for `YieldStatement` nodes
207+
in the function body (excluding nested function/class definitions). Functions
208+
containing yield have `IsGenerator = true` on their symbol.*

0 commit comments

Comments
 (0)