What happens
PythonGenerator writes a class's interfaces into its base list verbatim. When an interface names the class being declared — the self-type idiom — the generated module raises on import, because Python evaluates a base list eagerly.
Input:
ClassDeclaration length = new("Length") { /* … */ };
length.Interfaces.Add(TypeReference.Parse("IVector0<Length<T>, T>"));
Output:
class Length(IVector0[Length[T], T]):
Value: T = None
Running it:
NameError: name 'Length' is not defined
Not a warning and not a runtime edge case — the module cannot be imported at all.
Why this shape and not an exotic one
IVector0<TSelf, T> is how an interface gives a method the implementing type as its return type — TSelf Create(T value) rather than IVector0 Create(T value). Every one of the 212 generated quantities in ktsu.Semantics is declared this way, and it is ordinary in C#, C++ (CRTP) and Rust (trait Foo where Self: Sized). It is not a corner.
The same is true of any mutually-referential pair, which needs no generics at all:
class Node(Visitor[Node]): # NameError
Why it is Python's to solve rather than the AST's
The AST is right: the class does implement an interface named over itself, and six targets write that correctly. Python is the one that evaluates the base list before the name exists.
Python has two answers and the generator currently uses neither:
from __future__ import annotations — makes annotations lazy, which fixes a self-reference in a field or return type but not in a base list, since that is an expression rather than an annotation.
- A string —
class Length(IVector0["Length[T]", "T"]) — which typing resolves lazily. This is the one that works here, and is what typing.Generic consumers write for exactly this case.
So the fix is narrow: when a base names the class being declared, write that argument as a string. Everything else about the base list stays as it is.
Missing coverage
There is no Python test with a self-referential interface, and no Python equivalent of CGeneratedSourceCompilesTests — nothing runs what the generator writes. python3 is as available as g++ and go, and importing a generated module is the same shape of check the other four targets already get: it would have caught this, and it is the only way to catch the class of error where the text looks right and the interpreter disagrees.
How it was found
Building one ktsu.Semantics quantity as a neutral AST and writing it in all seven targets. Five produce what they should; this is one of the two that do not. The other is #63 (Go).
What happens
PythonGeneratorwrites a class's interfaces into its base list verbatim. When an interface names the class being declared — the self-type idiom — the generated module raises on import, because Python evaluates a base list eagerly.Input:
Output:
Running it:
Not a warning and not a runtime edge case — the module cannot be imported at all.
Why this shape and not an exotic one
IVector0<TSelf, T>is how an interface gives a method the implementing type as its return type —TSelf Create(T value)rather thanIVector0 Create(T value). Every one of the 212 generated quantities inktsu.Semanticsis declared this way, and it is ordinary in C#, C++ (CRTP) and Rust (trait Foo where Self: Sized). It is not a corner.The same is true of any mutually-referential pair, which needs no generics at all:
Why it is Python's to solve rather than the AST's
The AST is right: the class does implement an interface named over itself, and six targets write that correctly. Python is the one that evaluates the base list before the name exists.
Python has two answers and the generator currently uses neither:
from __future__ import annotations— makes annotations lazy, which fixes a self-reference in a field or return type but not in a base list, since that is an expression rather than an annotation.class Length(IVector0["Length[T]", "T"])— whichtypingresolves lazily. This is the one that works here, and is whattyping.Genericconsumers write for exactly this case.So the fix is narrow: when a base names the class being declared, write that argument as a string. Everything else about the base list stays as it is.
Missing coverage
There is no Python test with a self-referential interface, and no Python equivalent of
CGeneratedSourceCompilesTests— nothing runs what the generator writes.python3is as available asg++andgo, and importing a generated module is the same shape of check the other four targets already get: it would have caught this, and it is the only way to catch the class of error where the text looks right and the interpreter disagrees.How it was found
Building one
ktsu.Semanticsquantity as a neutral AST and writing it in all seven targets. Five produce what they should; this is one of the two that do not. The other is #63 (Go).