What's wrong
PythonGenerator writes parameter, return and class-attribute annotations with the bare type name (PythonTypeFromGenericType, Coder/Languages/PythonGenerator.cs:677). The call sites are GenerateFieldDeclaration at :310, the parameter emitter near :661 and the return hint in the function and method signatures. Python before 3.14 evaluates these annotations eagerly: when the def statement runs, and when a class-body variable annotation runs. Suppose an annotation names the class whose body is still executing, or a class declared further down the file. That name does not exist yet, so the module fails to import.
This is the same failure #64 fixed for base lists. PythonBaseFromGenericType (:732) quotes a base argument that names the declaring class. The fix did not reach annotations, and the remarks at :720 say so explicitly. They point to from __future__ import annotations as "the other half of the idea", but the generator never emits it. SpellImport (:169) only produces import X, so a caller cannot add it through SourceFile.Imports either.
The shape is extremely common. Examples: a factory returning its own type (the Point.zero() in CompiledExemplar.Point()), a method taking another instance (distance(self, other: Point), any operator-like method), and a linked node (next: Node).
Reproduction
var point = new ClassDeclaration("Point");
point.Members.Add(new FieldDeclaration("x", "int"));
var zero = new FunctionDeclaration("zero") { ReturnType = "Point", IsStatic = true };
zero.Body.Add(new ReturnStatement(new ConstructionExpression(new TypeReference("Point"))));
point.Members.Add(zero);
var dist = new FunctionDeclaration("distance") { ReturnType = "int" };
dist.Parameters.Add(new Parameter("other", "Point"));
dist.Body.Add(new ReturnStatement(new LiteralExpression<int>(0)));
point.Members.Add(dist);
var node = new ClassDeclaration("Node");
node.Members.Add(new FieldDeclaration("next", "Node"));
Console.WriteLine(new PythonGenerator().Generate(new SourceFile("shapes") { Members = { point, node } }));
Generated:
class Point:
x: int
@staticmethod
def zero() -> Point:
return Point()
def distance(self, other: Point) -> int:
return 0
class Node:
next: Node
Actual result under Python 3.11 (python3 -c "import shapes"):
File "shapes.py", line 5, in Point
def zero() -> Point:
^^^^^
NameError: name 'Point' is not defined. Did you mean: 'print'?
Generating Node on its own fails the same way: next: Node gives NameError: name 'Node' is not defined.
Expected: the module imports. I prepended from __future__ import annotations to the same output, and it imported and ran (fixed.Point.zero() returned a Point).
Why it matters
Any generated Python class that mentions its own type in a signature or field cannot be imported at all. PythonGeneratedSourceImportsTests exists to catch exactly this class of error, but its exemplar has no annotation naming the class, so the test passes.
Suggested fix / acceptance criteria
- Pick one of these two fixes:
- Emit
from __future__ import annotations as the first statement of a generated Python SourceFile. For example, override WriteFileDirectives, which already exists for C++ and Go. It must come before any import; the header comment can stay above it.
- Quote any annotation that names the enclosing class or a class declared later, the same way
PythonBaseFromGenericType quotes base arguments. This also covers a class generated on its own, outside a SourceFile.
- Extend
PythonGeneratedSourceImportsTests so its exemplar includes the following, and the module still loads:
- a static method returning the declaring class
- a parameter typed as the declaring class
- a field typed as the declaring class
- a parameter typed as a class declared later in the file
What's wrong
PythonGeneratorwrites parameter, return and class-attribute annotations with the bare type name (PythonTypeFromGenericType,Coder/Languages/PythonGenerator.cs:677). The call sites areGenerateFieldDeclarationat:310, the parameter emitter near:661and the return hint in the function and method signatures. Python before 3.14 evaluates these annotations eagerly: when thedefstatement runs, and when a class-body variable annotation runs. Suppose an annotation names the class whose body is still executing, or a class declared further down the file. That name does not exist yet, so the module fails to import.This is the same failure #64 fixed for base lists.
PythonBaseFromGenericType(:732) quotes a base argument that names the declaring class. The fix did not reach annotations, and the remarks at:720say so explicitly. They point tofrom __future__ import annotationsas "the other half of the idea", but the generator never emits it.SpellImport(:169) only producesimport X, so a caller cannot add it throughSourceFile.Importseither.The shape is extremely common. Examples: a factory returning its own type (the
Point.zero()inCompiledExemplar.Point()), a method taking another instance (distance(self, other: Point), any operator-like method), and a linked node (next: Node).Reproduction
Generated:
Actual result under Python 3.11 (
python3 -c "import shapes"):Generating
Nodeon its own fails the same way:next: NodegivesNameError: name 'Node' is not defined.Expected: the module imports. I prepended
from __future__ import annotationsto the same output, and it imported and ran (fixed.Point.zero()returned aPoint).Why it matters
Any generated Python class that mentions its own type in a signature or field cannot be imported at all.
PythonGeneratedSourceImportsTestsexists to catch exactly this class of error, but its exemplar has no annotation naming the class, so the test passes.Suggested fix / acceptance criteria
from __future__ import annotationsas the first statement of a generated PythonSourceFile. For example, overrideWriteFileDirectives, which already exists for C++ and Go. It must come before any import; the header comment can stay above it.PythonBaseFromGenericTypequotes base arguments. This also covers a class generated on its own, outside aSourceFile.PythonGeneratedSourceImportsTestsso its exemplar includes the following, and the module still loads: