Skip to content

Go writes a generic type it did not declare, so the generated package does not compile #63

Description

@matt-edmondson

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions