Skip to content
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

spec: are local types in different instantiations of a generic function identical? #58573

Closed
adonovan opened this issue Feb 16, 2023 · 8 comments
Labels
NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made.

Comments

@adonovan
Copy link
Member

Are local types within generic functions distinct for each instantiation, even when they don't reference the type parameters? The current implementation says yes, but I wonder whether this is required.

See https://go.dev/play/p/3taZi53yieT for example. Each instantiation of f[T] with a different type creates a distinct local type:

var m = make(map[reflect.Type]bool)

func f[T any]() {
	type local struct{} // does not reference T
	m[reflect.TypeOf(local{})] = true
	fmt.Println(len(m))
}

f[string]() // prints 1
f[int]()    // prints 2

I think the spec should take a position one way or the other since the effect is so readily observable (unlike function equality, which we can prevent users from asking questions about). The simplest solution would be to require that the local types are distinct for each instantiation, so that "we don't have to define what it means for a local type to depend on a type parameter", as @ianlancetaylor says, noting also that "making the types distinct leads to implementation complexity if we can otherwise treat F[int] and F[MyInt] as being the same".

@ianlancetaylor @griesemer

@griesemer
Copy link
Contributor

I think the argument here is that each distinct instantiation of a generic function produces a distinct non-generic function. Each of those functions has its own distinct local types, very much like any other non-generic function.

@mdempsky
Copy link
Contributor

mdempsky commented Feb 17, 2023

I agree that each distinct instantiation should produce a distinct defined type, regardless of which type parameters the underlying type references.

Users can always manually hoist the local defined type to package scope and explicitly add type parameters if they want finer-grained control over which type parameters affect its identity.

@mdempsky mdempsky added the NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made. label Feb 17, 2023
@timothy-king
Copy link
Contributor

https://go.dev/play/p/ARWTgThVoLC shows that the current implementation can have an exponential number of distinct local types. None of these require type parameters. This is a silly example, but it does worry me a bit. Could requiring this behavior in the spec cause future problems?

@mdempsky
Copy link
Contributor

@timothy-king I'm personally not concerned. You could construct an exponential number of type instantiations without local types: https://go.dev/play/p/2JZSLoIS9if?v=goprev

The type checker includes logic to make sure that instantiations are always finite.

Beyond that, I think it's up to users to make sure that finite number fits within their available build resources. E.g., we don't place any limitations on the number of unique type or function declarations a user can explicitly write in a source file.

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/467995 mentions this issue: go/ssa: substitute type parameters in local types

gopherbot pushed a commit to golang/tools that referenced this issue Feb 17, 2023
ssa.subster now supports substituting type parameters that are
within local Named types.

Generalizes the definition of isParameterized to support local
Named types that contain type parameters that are not instantiated.

Fixes golang/go#58491
Updates golang/go#58573

Change-Id: Id7a4e45c41126056105e483b59eb057c05786d66
Reviewed-on: https://go-review.googlesource.com/c/tools/+/467995
TryBot-Result: Gopher Robot <gobot@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
Run-TryBot: Tim King <taking@google.com>
@zigo101
Copy link

zigo101 commented Apr 15, 2023

There is a situation preferring identical instantiation: a non-generic function is changed to a generic one for some reason, but the author doesn't realize the result.

@adonovan
Copy link
Member Author

There is a situation preferring identical instantiation: a non-generic function is changed to a generic one for some reason, but the author doesn't realize the result.

Yes, either choice is possibly surprising to someone. Treating type identity specially for nfreetypevars(T)=0 is a bit like treating new(T) specially where sizeof(T)=0.

@griesemer
Copy link
Contributor

This has effectively been decided and implemented as discussed in the beginning. Leaving #65152 open for documentation and closing this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsDecision Feedback is required from experts, contributors, and/or the community before a change can be made.
Projects
None yet
Development

No branches or pull requests

6 participants