Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Adding nested type support #1370

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 22, 2025
Merged

Conversation

grantnelson-wf
Copy link
Collaborator

@grantnelson-wf grantnelson-wf commented May 12, 2025

Summary

Adding type parameters from a function into the nested types inside of that function. For example:

func Foo[T any]() {
	type Bar struct { x T }
	type Baz[U any] struct { x T; y U }
	...
}

The type Bar is concrete (i.e. not generic) however it acts like it is generic because it is nested in a generic function. If the function is called like Foo[string]() then Bar innately is different from if the function is called like Foo[int](). The nesting type parameters is shown like Bar[T;] and instantiated like Bar[string;] and Bar[int;].

In Go the ; in the type parameters isn't present if the nested type is concrete. Baz is generic meaning the ; indicates the separation between the nested type parameters and the objects own type parameters. For example if Baz[string]{} is expressed inside of Foo[int], then a Baz[int; string] is instantiated. If Baz[T]{} is expressed inside of Foo[X], then a Baz[X; X] is instantiated.

When creating instances for a nested type, all combinations of nest function instances and nesting type instances need to be determined. For example if Foo has the instances Foo[string] and Foo[int], and Baz has the instances Baz[bool] and Baz[string], then the resolved instances for Baz are Baz[string; bool], Baz[string; string], Baz[int; bool], and Baz[int; string]. While reading the instances from types.Info, an instance may have a type parameter in it, e.g. Baz[T]. We replace the type parameter with the nest function's instance type arguments, meaning Baz[T] resolves to Baz[int; int] and Baz[string; string], but not Baz[int; string] nor Baz[string; int].

Note: I used TNest or similar to store the type arguments from the nest function or method as a separate set from TArgs. This is different from what Go does which is using an index for the "implicit" type arguments to differentiate between the two parts, seen here. I felt it was easier to maintain and to fit into the existing code better than using an index.

This is related to #1013 and #1270


Future Work / Known Issues

Fixing Nested Type Arguments

This change does not include a fix for when a type argument itself is nested. For example:

func Foo[T any]() {
	type Bar struct { X T }
	type Baz[U any] struct { }
	_ = Baz[Bar]{}
	...
}

The Baz is instantiated correctly using the nesting type argument for Foo and the argument given to it Bar. However, Bar has not been instantiated correctly with the nesting type argument for Foo, meaning Bar is still generic with the underlying type of struct { X T }. If Foo[int]() is called, the Bar used for the type argument for Baz needs to be Bar[int;] with struct { X int }.

I didn't fix this issue yet because it feels very unlikely, although possible, to happen, and I found it while finishing up this PR. So, instead of delaying this initial work on nested types, I left some tests that demonstrate the issue and skipped them with a TODO(grantnelson-wf) comment.

Fixing DCE

This change does not include an update to the DCE to remove unused instances caused by nested types. For example:

func Foo[T any]() {
	type Bar struct { }
	...
}

func aliveCode() {
	Foo[string]()
}

func deadCode() {
	Foo[int]()
}

In the above code the instances Foo[string] and Foo[int] are created, then Foo[int] is found to be dead and eliminated. However, the instance Bar[string;] and Bar[int;] are created by their respective nest function instances. Right now our DCE doesn't take into account the nesting type parameters so will see all instances of Bar as alive because "Bar" is used in the alive Foo[string]. This leaves the dead code Bar[int], also seen as "Bar", alive when it should be eliminated.

A following ticket will fix the DCE to take into acount the nesting type parameters so that dead nested types will be properly removed.

Numbering Nested Types Issue

This change does not include any of the numbering of types like Go does. For example:

type Biz[U any] struct{}

func Foo() any {
	type Baz struct{}
	return Biz[Baz]{}
}

func Bar() any {
	type Baz struct{}
	return Biz[Baz]{}
}

When the types returned from Foo and Bar are printed, they are main.Biz[main.Baz·1] and main.Biz[main.Baz·2] respectively. The ·1 and ·2 are indications of which declaration of Baz is being used. They are not shown in all cases and never show for package level declarations. The numbering is applied in the order the files are parsed and nested types are declared in those files.

The number increases monotonically with each new type declaration regardless of their name and type, meaning if the Baz in Bar is changed to type Cat interface{} and Biz is instantiated with Cat in Bar, the types are main.Biz[main.Baz·1] and main.Biz[main.Cat·2]. The type from Foo didn't change and Cat is still denoted with a ·2 since it is the second nested type.

Since these numbers aren't being collected nor printed, we can not remove typeparam/nested.go from the known failures of the Gorepo Tests yet. The test will not pass because the outputs will not match without the nested type numbering. So, not only are does that test hit some "Nested Type Arguments" (the main.U's aren't being instantiated correctly), but the difference in the outputs looks like:

+ 0,3: main.T[int;int]
+ 4,7: main.T[int;main.U[int]]
+ 22,23: main.T[main.Int;main.Int]
+ 26,27: main.T[main.Int;main.U[main.Int]]
- 0,3: main.T·2[int;int]
- 4,7: main.T·2[int;main.U·3[int;int]]
- 22,23: main.T·2[main.Int;main.Int]
- 26,27: main.T·2[main.Int;main.U·3[main.Int;main.Int]]

@grantnelson-wf grantnelson-wf self-assigned this May 12, 2025
@grantnelson-wf grantnelson-wf changed the title Adding type parameters from a function into the nested types inside of that function Adding nested type support May 12, 2025
@grantnelson-wf grantnelson-wf marked this pull request as ready for review May 14, 2025 17:31
@grantnelson-wf grantnelson-wf merged commit 208d830 into gopherjs:master May 22, 2025
10 checks passed
@grantnelson-wf grantnelson-wf deleted the nestedTypes branch May 22, 2025 18:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.