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

proposal: spec: allow type conversion from generic type to same underlying type #61164

Open
rogpeppe opened this issue Jul 4, 2023 · 9 comments
Labels
LanguageChange Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee Proposal
Milestone

Comments

@rogpeppe
Copy link
Contributor

rogpeppe commented Jul 4, 2023

What version of Go are you using (go version)?

$ go version
go version devel go1.21-f90b4cd655 Fri May 26 03:21:41 2023 +0000 linux/amd64

Does this issue reproduce with the latest release?

Yes

What did you do?

I compiled this code:

type A struct {
	a int
}
type B A

func F[T A | B](x *T) {
	x1 := (*A)(x)
	_ = x1
}

What did you expect to see?

Compilation success. We know that all of the possible types of *T can be converted to *A so the conversion could be OK.

What did you see instead?

./prog.go:9:13: cannot convert x (variable of type *T) to type *A

To work around this, we can use a type switch: https://go.dev/play/p/PfuuWgA4eNG

func F[T A | B](x *T) {
	var x1 *A
	switch x := any(x).(type) {
	case *A:
		x1 = x
	case *B:
		x1 = (*A)(x)
	case *string:
		x1 = nil
	}
	_ = x1
}

But this is considerably more verbose and also more error-prone, because it fail at run time if one of the types in the type set isn't mentioned in the type switch.

@rogpeppe rogpeppe changed the title spec: allow type conversion to identical type from generic type spec: allow type conversion to identical type from generic type with same underlying type Jul 4, 2023
@rogpeppe rogpeppe changed the title spec: allow type conversion to identical type from generic type with same underlying type spec: allow type conversion from generic type to same underlying type Jul 4, 2023
@seankhliao
Copy link
Member

is the issue restricted to pointers?

spec:

Additionally, if x's type V or T are type parameters, x is assignable to a variable of type T if one of the following conditions applies:

  • ...
  • V is a type parameter and T is not a named type, and values of each type in V's type set are assignable to T.

https://go.dev/play/p/7hC-DCKDGYI

@zigo101
Copy link

zigo101 commented Jul 5, 2023

see: #50421

@rogpeppe
Copy link
Contributor Author

rogpeppe commented Jul 7, 2023

@seankhliao:
I think this particular issue is related to pointers.

As you pointed out, the conversion is allowed if the values are type parameter-typed.

ignoring struct tags (see below), x's type and T are pointer types that are not named types, and their pointer base types are not type parameters but have identical underlying types.

This could allow pointers to type parameters, I think.

cueckoo pushed a commit to cue-lang/cue-trybot that referenced this issue Jul 7, 2023
By putting the pointer inside the type parameter, we can do
a type conversion without a dynamic type conversion.

See golang/go#61164 for context.

Signed-off-by: Roger Peppe <rogpeppe@gmail.com>
Change-Id: I0af66b474aaf840bddec933a89bc168843712674
Dispatch-Trailer: {"type":"trybot","CL":556442,"patchset":1,"ref":"refs/changes/42/556442/1","targetBranch":"master"}
@cherrymui
Copy link
Member

This looks like a (small) language change. Perhaps it needs a proposal.

cc @griesemer @ianlancetaylor @golang/compiler

@cherrymui cherrymui added NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. LanguageChange Suggested changes to the Go language labels Jul 11, 2023
@ianlancetaylor
Copy link
Member

I agree that the spec doesn't currently permit this conversion. The spec says a conversion is permitted if, among other things:

ignoring struct tags (see below), x's type and T are pointer types that are not named types, and their pointer base types are not type parameters but have identical underlying types.

There is no case that permits conversions of pointers to different type parameters.

@griesemer
Copy link
Contributor

Agreed that the rules around conversions of pointer types involving type parameters could use some finessing.

@septemhill
Copy link

May I ask, if we do approve this, does it mean the following code would be allowed ?

func tildeBaseCast[T any, E ~T](v E) T {
	return T(v)
}

func tildBasePtrCast[T any, E ~T](v *E) *T {
	return (*T)(v)
}

type IntAlias int

var ia IntAlias = 123

v1 := tildeBaseCast[int, IntAlias](ia)  // type of v1 would be `int`
v2 := tildeBasePtrCast[int, IntAlias](&ia) // type of v2 would be `*int`

@zigo101
Copy link

zigo101 commented Apr 23, 2024

@septemhill

Certainly not. This is a problem unrelated to type arguments.

@seankhliao seankhliao added this to the Unplanned milestone Jul 13, 2024
@ianlancetaylor ianlancetaylor changed the title spec: allow type conversion from generic type to same underlying type proposal: spec: allow type conversion from generic type to same underlying type Aug 6, 2024
@ianlancetaylor ianlancetaylor added Proposal LanguageChangeReview Discussed by language change review committee and removed NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Aug 6, 2024
@arvidfm
Copy link

arvidfm commented Aug 24, 2024

This would be very nice to have to allow you to "normalise" pointers, eliminating the need to keep track of the type parameter. Consider a type that writes to a pointer to e.g. a struct field:

// this function can take a pointer to any field type
// whose underlying type is uint...
func NewUintIncrementer[T ~uint](dst *T) UintIncrementer {
    return UintIncrementer{
        Dst: (*uint)(dst), // currently doesn't work
    }
}

// ...but this type doesn't need to know about the actual
// type of the field
type UintIncrementer struct {
    Dst *uint
}

func (us UintIncrementer) Increment() {
    *us.Dst += 1
}

Right now, since the pointer conversion above doesn't work, you would have to make UintIncrementer generic too, meaning you have to juggle more type parameters, and the type potentially needs to be instantiated more times (though maybe Go's shape-based stencilling would help here?).

Since a conversion like (*Uint)(x) (with var x *uint and type Uint uint) works fine, it seems like it would be a natural extension to allow this to work for pointers to type parameters whose underlying type is the same as the value being converted.

Not sure if this union-less example warrants its own issue, since it might be easier to implement than the version in the OP?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
LanguageChange Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee Proposal
Projects
None yet
Development

No branches or pull requests

8 participants