Skip to content

proposal: Go 2: type switch case statements with multiple interface types should produce an intersection of methods #65031

Closed as not planned
@lu4p

Description

@lu4p

Go Programming Experience

Experienced

Other Languages Experience

Go, Rust, TS/JS, C/C++, Python

Related Idea

  • Has this idea, or one like it, been proposed before?
  • Does this affect error handling?
  • Is this about generics?
  • 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?

No.

Does this affect error handling?

No.

Is this about generics?

No.

Proposal

// typ is of type types.Type
switch newTyp := typ.(type) {
case *types.Named, *types.Interface:

}

Currently newTyp in the case statement has the types.Type type:

type Type interface {
	Underlying() Type
	String() string
}

Proposed change:
Instead it should have the type of an implicitly generated interface, which consists of the intersection of all methods both types have in common.

In current go this would be:

switch newTyp := typ.(type) {
case *types.Named, *types.Interface:
	{
		newTyp := newTyp.(interface {
			Method(i int) *types.Func
			NumMethods() int
			Underlying() types.Type
			String() string
		})
	}
}

I know that this helps people who analyze go code, because it allows for more concise type switches, which are very common when analyzing code. There are probably other use cases, like parsers, anytime when there is a type switch to narrow down the type and then calling a method on that type.

It would also be nice if this works for the intersection of struct fields, but I think additional work on generics is required first, in order to have a similarly low cost "syntactic sugar" implementation.

Language Spec Changes

This new behavior would need to be documented under https://go.dev/ref/spec#Type_switches

Informal Change

In case statements with multiple types, the switched type is of an interface which contains the intersection of both types methods.

Is this change backward compatible?

Strictly speaking no, because the output of reflect.TypeOf may change, however I don't think this will break programs. reflect.TypeOf could pretend that the implicitly generated interface doesn't exist and tell the caller that typ is of type types.Type, then it would be backward compatible.

Before the change (this is real code from here):

switch typ := typ.(type) {
case *types.Named:
	for i := 0; i < typ.NumMethods(); i++ {
		if m := typ.Method(i); token.IsExported(m.Name()) && isSupportedSig(m) {
			methods = append(methods, m)
			if len(methods) > limitFunctionCount {
				break
			}
		}
	}
case *types.Interface:
	for i := 0; i < typ.NumMethods(); i++ {
		if m := typ.Method(i); token.IsExported(m.Name()) && isSupportedSig(m) {
			methods = append(methods, m)
			if len(methods) > limitFunctionCount {
				break
			}
		}
	}
}

After the change:

switch typ := typ.(type) {
case *types.Named, *types.Interface:
	for i := 0; i < typ.NumMethods(); i++ {
		if m := typ.Method(i); token.IsExported(m.Name()) && isSupportedSig(m) {
			methods = append(methods, m)
			if len(methods) > limitFunctionCount {
				break
			}
		}
	}
}

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

This should make go code easier to read and write.

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

Easier, I think the current behavior is non intuitive.

Cost Description

This requires someone to implement the transformation in the compiler. Also gopls needs to be updated to know that this is something the compiler does. And someone needs to update the language spec.

Changes to Go ToolChain

gopls, compile

Performance Costs

Some small compile time cost, no run time cost.

Prototype

This code:

switch typ := typ.(type) {
case *types.Named, *types.Interface:
	for i := 0; i < typ.NumMethods(); i++ {
		if m := typ.Method(i); token.IsExported(m.Name()) && isSupportedSig(m) {
			methods = append(methods, m)
			if len(methods) > limitFunctionCount {
				break
			}
		}
	}
}

Could be converted to:

switch typ := typ.(type) {
case *types.Named, *types.Interface:
	{
		typ := typ.(interface {
			Method(i int) *types.Func
			NumMethods() int
			Underlying() types.Type
			String() string
		})
               for i := 0; i < typ.NumMethods(); i++ {
		    if m := typ.Method(i); token.IsExported(m.Name()) && isSupportedSig(m) {
			    methods = append(methods, m)
			    if len(methods) > limitFunctionCount {
		                break
			    }
		    }
	       }
	}
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions