Repository: go
Issue: proposal: spec: Values assigned to multi-case branches in type switches should have generics-style 
Labels: LanguageChange,Proposal,Proposal-Hold,generics,LanguageChangeReview

### Go Programming Experience

Experienced

### Other Languages Experience

C, C++, Python, Rust, Haskell

### Related Idea

- [ ] Has this idea, or one like it, been proposed before?
- [ ] Does this affect error handling?
- [X] Is this about generics?
- [X] Is this change backward compatible? Breaking the Go 1 compatibility guarantee is a large cost and requires a large benefit

### Has this idea, or one like it, been proposed before?

https://github.com/golang/go/issues/65031 seems similar, although the details differ.

https://github.com/golang/go/issues/57644 is possibly related, but I can't tell from its description what effect (if any) it would have on type switches.

### Does this affect error handling?

No

### Is this about generics?

Yes -- this proposal is to make multi-case type switches match the semantics of generic functions with a set of permitted types.

### Proposal

In Go 1.23, a value assigned as the match result in a multi-case type switch retains the original type being inspected. This prevents it from being passed to a function with a type parameter constraint that's only satisfied by the narrowed type.

```go
func fmtUint[T uint8 | uint16 | uint32 | uint64](value T) string {
	return strconv.FormatUint(uint64(value), 10)
}

// error: any does not satisfy uint8 | uint16 | uint32 | uint64
func fmtValue(value any) string {
	switch v := value.(type) {
	case uint8, uint16, uint32, uint64:
		// `v` is known to be of the above listed concrete types,
		// but its type remains `interface{}`, so it can't be used as `T`.
		return fmtUint(v)
	}
	return fmt.Sprintf("%v", value)
}

// semantically equivalent, and valid in Go 1.23, but overly verbose
func fmtValue(value any) string {
	switch v := value.(type) {
	case uint8:
		return fmtUint(v)
	case uint16:
		return fmtUint(v)
	case uint32:
		return fmtUint(v)
	case uint64:
		return fmtUint(v)
	}
	return fmt.Sprintf("%v", value)
}
```

I propose to change the semantics of multi-case type switches so that the type of the matched variable becomes the intersection of the matched types, plus the original type:

```go
interface {
	// To preserve existing behavior, `v` can be treated as its original type.
	any

	// `v` can also satisfy type parameter conditions that permit all of the types
	// in its case match.
	( uint8 | uint16 | uint32 | uint64 )
}
```

The matched case must have types that are a subset of the generic type:
```go
// OK: `v` might be a `uint8` or `uint16`, but both of those are acceptable to `fmtUint`
func fmtValue(value any) string {
	switch v := value.(type) {
	case uint8, uint16:
		return fmtUint(v)
	}
	return fmt.Sprintf("%v", value)
}

// Error: `v` might be `uintptr`, so the set of possible types is a superset of those
// accepted by`fmtUint`.
func fmtValue(value any) string {
	switch v := value.(type) {
	case uint8, uint16, uint32, uint64, uintptr:
		return fmtUint(v)
	}
	return fmt.Sprintf("%v", value)
}
```

The type of `v` should also work with the semantics of a generic type parameter for regular code within the case, for example performing conversions that are valid for all matchable types:
```go
// OK: all of the matched types can be cast to uint64.
func fmtValueHex(value any) string {
	switch v := value.(type) {
	case uint8, uint16, uint32, uint64:
		return strconv.FormatUint(uint64(v), 16)
	}
	return fmt.Sprintf("%v", value)
}
```

### Language Spec Changes

_No response_

### Informal Change

_No response_

### Is this change backward compatible?

I think so? Given existing and proposed behavior, I believe that any existing code would continue to compile and run without changes.

### Orthogonality: How does this change interact or overlap with existing features?

_No response_

### Would this change make Go easier or harder to learn, and why?

_No response_

### Cost Description

_No response_

### Changes to Go ToolChain

_No response_

### Performance Costs

_No response_

### Prototype

_No response_