What happens
A ClassDeclaration with a TypeParameter comes out of GoGenerator as a struct with no parameters — which is the documented decision — but the function that returns it still spells the type as generic, and the field keeps the parameter's name as its type.
Input, the shape every ktsu.Semantics quantity has:
ClassDeclaration length = new("Length") { Kind = TypeDeclarationKind.Struct, /* … */ };
length.TypeParameters.Add(TypeParameter.Parse("T : struct, INumber<T>"));
length.Members.Add(new PropertyDeclaration("Value") { Type = TypeReference.Parse("T"), /* … */ });
FunctionDeclaration factory = new("FromMeter") { ReturnType = TypeReference.Parse("Length<T>"), IsStatic = true };
factory.Parameters.Add(new Parameter("value", "T"));
Output:
// over T : struct, INumber<T>
type Length struct {
Value T
}
func LengthFromMeter(value T) Length[T] {
return Create(value)
}
go vet on that file:
vet: ./length.go:9:8: undefined: T
Three things disagree in six lines: the type is declared without parameters, T is used as a field type and as a parameter type, and the return type is written Length[T] — subscripting a type that takes no parameters.
Why it is a defect rather than the documented decision
CLAUDE.md states the decision clearly, and it is a reasonable one:
Go spells one, Implements being exactly a Go constraint interface, and only for a function: a method on a generic type needs the parameters in three places and spelled two ways (NewPoint for the constructor's name, Point[T] for its receiver and result), so a generic type is written down instead.
Writing the type down is the decision. Writing it down and then referring to it as generic anyway is not — it is the note and the emission disagreeing. Whichever way it resolves, the two have to agree:
- If a generic type is written down, then everything that mentions it should be written down too, and the file should contain no
Length[T] and no bare T.
- If
Length[T any] is emitted instead, the note goes away and the parameters appear in all three places the note says they are needed.
The first is smaller and matches the stated decision. The second is what a Go user would want, and Go has had type parameters since 1.18.
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 #— (Python, filed alongside).
Missing coverage
GoGeneratedSourceCompilesTests compiles what the generator writes, but no case in it declares a type parameter on a ClassDeclaration. RustGeneratedSourceCompilesTests does — impl<T: Bound> Mass<T> is compiled there — which is why Rust's answer is right and this one is not.
Suggested fix
Decide which way the disagreement resolves, then add a GoGeneratedSourceCompilesTests case with a type parameter on the class so the answer is compiled rather than asserted. A struct with a parameterised field and a constructor returning the type is enough to catch every part of this.
What happens
A
ClassDeclarationwith aTypeParametercomes out ofGoGeneratoras a struct with no parameters — which is the documented decision — but the function that returns it still spells the type as generic, and the field keeps the parameter's name as its type.Input, the shape every
ktsu.Semanticsquantity has:Output:
go veton that file:Three things disagree in six lines: the type is declared without parameters,
Tis used as a field type and as a parameter type, and the return type is writtenLength[T]— subscripting a type that takes no parameters.Why it is a defect rather than the documented decision
CLAUDE.mdstates the decision clearly, and it is a reasonable one:Writing the type down is the decision. Writing it down and then referring to it as generic anyway is not — it is the note and the emission disagreeing. Whichever way it resolves, the two have to agree:
Length[T]and no bareT.Length[T any]is emitted instead, the note goes away and the parameters appear in all three places the note says they are needed.The first is smaller and matches the stated decision. The second is what a Go user would want, and Go has had type parameters since 1.18.
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 #— (Python, filed alongside).Missing coverage
GoGeneratedSourceCompilesTestscompiles what the generator writes, but no case in it declares a type parameter on aClassDeclaration.RustGeneratedSourceCompilesTestsdoes —impl<T: Bound> Mass<T>is compiled there — which is why Rust's answer is right and this one is not.Suggested fix
Decide which way the disagreement resolves, then add a
GoGeneratedSourceCompilesTestscase with a type parameter on the class so the answer is compiled rather than asserted. A struct with a parameterised field and a constructor returning the type is enough to catch every part of this.