Skip to content

Commit 12d8609

Browse files
antonsyndclaude
andcommitted
fix: handle for/while ElseBody in generator traversals and remove stale symbol fallback
- ContainsYield() now checks ForStatement.ElseBody and WhileStatement.ElseBody, ensuring yield in for/while else blocks correctly marks the function as a generator. - FindReturnWithValue() now checks ForStatement.ElseBody and WhileStatement.ElseBody, ensuring return-with-value in those blocks triggers SPY0267. - Removed stale functionSymbol fallback in generator symbol update path; replaced with Debug.Assert + null guard to surface bugs instead of silently succeeding. - Removed redundant Shared. namespace qualifier in DunderDetector call. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c71112f commit 12d8609

2 files changed

Lines changed: 29 additions & 15 deletions

File tree

src/Sharpy.Compiler/Semantic/TypeChecker.Definitions.cs

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Diagnostics;
12
using Sharpy.Compiler.Parser.Ast;
23
using Sharpy.Compiler.Diagnostics;
34
using Sharpy.Compiler.Logging;
@@ -387,23 +388,26 @@ private void CheckFunction(FunctionDef functionDef)
387388
// reference stored in ProtocolMethods/OperatorMethods.
388389
// For top-level functions, fall back to the symbol table.
389390
var currentSymbol = _currentClass?.Methods.FirstOrDefault(m => m.Name == functionDef.Name && m.DeclarationLine == functionDef.LineStart)
390-
?? _symbolTable.Lookup(functionDef.Name) as FunctionSymbol
391-
?? functionSymbol;
392-
currentSymbol.IsGenerator = true;
393-
394-
// For non-dunder generators (standalone functions and regular class methods),
395-
// wrap the return type so callers see IEnumerable<T> instead of T.
396-
// Dunder methods (__iter__, __reversed__) keep their element type as ReturnType
397-
// because SynthesisAnalyzer reads it to determine interface type arguments,
398-
// and codegen handles the wrapping independently via AST annotations.
399-
var isDunder = Shared.DunderDetector.IsDunderMethod(functionDef.Name);
400-
if (!isDunder && currentSymbol.ReturnType is not (VoidType or UnknownType))
391+
?? _symbolTable.Lookup(functionDef.Name) as FunctionSymbol;
392+
Debug.Assert(currentSymbol != null, $"Generator function '{functionDef.Name}' not found in Methods list or SymbolTable");
393+
if (currentSymbol != null)
401394
{
402-
currentSymbol.ReturnType = new GenericType
395+
currentSymbol.IsGenerator = true;
396+
397+
// For non-dunder generators (standalone functions and regular class methods),
398+
// wrap the return type so callers see IEnumerable<T> instead of T.
399+
// Dunder methods (__iter__, __reversed__) keep their element type as ReturnType
400+
// because SynthesisAnalyzer reads it to determine interface type arguments,
401+
// and codegen handles the wrapping independently via AST annotations.
402+
var isDunder = DunderDetector.IsDunderMethod(functionDef.Name);
403+
if (!isDunder && currentSymbol.ReturnType is not (VoidType or UnknownType))
403404
{
404-
Name = "IEnumerable",
405-
TypeArguments = new List<SemanticType> { currentSymbol.ReturnType }
406-
};
405+
currentSymbol.ReturnType = new GenericType
406+
{
407+
Name = "IEnumerable",
408+
TypeArguments = new List<SemanticType> { currentSymbol.ReturnType }
409+
};
410+
}
407411
}
408412
}
409413
}
@@ -793,11 +797,15 @@ private static bool ContainsYield(System.Collections.Immutable.ImmutableArray<St
793797
{
794798
if (ContainsYield(whileStmt.Body))
795799
return true;
800+
if (ContainsYield(whileStmt.ElseBody))
801+
return true;
796802
}
797803
else if (stmt is ForStatement forStmt)
798804
{
799805
if (ContainsYield(forStmt.Body))
800806
return true;
807+
if (ContainsYield(forStmt.ElseBody))
808+
return true;
801809
}
802810
else if (stmt is TryStatement tryStmt)
803811
{

src/Sharpy.Compiler/Semantic/Validation/GeneratorValidator.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,18 @@ private void ValidateGeneratorFunction(FunctionDef funcDef, SemanticContext cont
144144
found = FindReturnWithValue(whileStmt.Body);
145145
if (found != null)
146146
return found;
147+
found = FindReturnWithValue(whileStmt.ElseBody);
148+
if (found != null)
149+
return found;
147150
}
148151
else if (stmt is ForStatement forStmt)
149152
{
150153
found = FindReturnWithValue(forStmt.Body);
151154
if (found != null)
152155
return found;
156+
found = FindReturnWithValue(forStmt.ElseBody);
157+
if (found != null)
158+
return found;
153159
}
154160
else if (stmt is TryStatement tryStmt)
155161
{

0 commit comments

Comments
 (0)